HauntMart
3 minutes to read
We are given the following website:
We also have the Python source code of the server (Flask).
First of all, we can register a new account and log in:
And we get to this dashboard:
The only functionality is “Sell Product”:
At this point, let’s analyze the source code.
Source code analysis
In blueprints/routes.py
, we find this endpoint that handles the previous funcionality:
@api.route('/product', methods=['POST'])
@isAuthenticated
def sellProduct(user):
if not request.is_json:
return response('Invalid JSON!'), 400
data = request.get_json()
name = data.get('name', '')
price = data.get('price', '')
description = data.get('description', '')
manualUrl = data.get('manual', '')
if not name or not price or not description or not manualUrl:
return response('All fields are required!'), 401
manualPath = downloadManual(manualUrl)
if (manualPath):
addProduct(name, description, price)
return response('Product submitted! Our mods will review your request')
return response('Invalid Manual URL!'), 400
It is calling downloadManual
, which is defined in util.py
:
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
As can be seen, if the url
passes the isSafeUrl
function, then the server performs an HTTP GET request and saves the result in a file.
The isSafeUrl
function basically checks that the URL does not contain some strings:
blocked_host = ["127.0.0.1", "localhost", "0.0.0.0"]
def isSafeUrl(url):
for hosts in blocked_host:
if hosts in url:
return False
return True
This function intends to block request to the same server. However, the blocked_host
list is not so extensive, and we can still perform requests to the server using other IP addresses formats, like 0x7f000001
or LOCALHOST
.
The target is an endpoint at /api/addAdmin
, which we can use to set any user as admin
. However, the request must be performed from the server (localhost
):
@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
Once we have an admin
user, we can go to the main page and read the flag:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>HauntMart | Home</title>
<link rel="stylesheet" href="/static/css/bootstrap.min.css">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined|Material+Icons" rel="stylesheet" />
<link rel="stylesheet" href="/static/css/home.css">
</head>
<body>
<!-- partial:index.partial.html -->
<nav class="navbar navbar-expand-lg bg-light mt-2 mb-2" data-bs-theme="light">
<div class="container-fluid">
<a class="navbar-brand" href="#">HauntMart</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarColor03"
aria-controls="navbarColor03" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarColor03">
<ul class="navbar-nav ms-auto">
{% if user['role'] == 'admin' %}
{{flag}}
{% endif %}
<li class="nav-item">
<a class="nav-link active" href="/home">Home
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/product">Sell Product</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Cart</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Profile</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/logout">Logout</a>
</li>
</ul>
</div>
</div>
</nav>
<main>
<!-- ... -->
</main>
<!-- partial -->
</body>
</html>
Solution
So, we can use the following URL to perform the request to /api/addAdmin
and modify our user account. This is known as Server-Side Request Forgery (SSRF), for more information, check HackTricks.
Flag
Finally, we need to log in again to refresh our session token and we will see the flag on the main page:
HTB{s5rf_m4d3_m3_w3t_my_p4nts!}