HauntMart
3 minutos de lectura
Se nos proporciona el siguiente sitio web:
También disponemos del código fuente de Python del servidor (Flask).
En primer lugar, podemos registrar una nueva cuenta e iniciar sesión:
Y llegamos a este dashboard:
La única funcionalidad es “Sell Product”:
En este punto, analicemos el código fuente.
Análisis del código fuente
En blueprints/routes.py
, encontramos este endpoint que maneja la funcionalidad anterior:
@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
Esta llamando a downloadManual
, que está definida en 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
Como se puede ver, si la url
pasa la función isSafeUrl
, Luego, el servidor realiza una petición HTTP por GET y guarda el resultado en un archivo.
La función isSafeUrl
básicamente verifica que la URL no contiene algunas 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
Esta función tiene la intención de bloquear peticiones al propio servidor. Sin embargo, la lista blocked_host
no es tan extensa, y aún podemos realizar peticiones al servidor utilizando otros formatos de direcciones IP, como 0x7f000001
o LOCALHOST
.
El objetivo es un endpoint en /api/addAdmin
, que podemos usar para establecer cualquier usuario como admin
. Sin embargo, la petición debe realizarse desde el servidor (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
Una vez que tenemos un usuario admin
, podemos ir a la página principal y leer la 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>
Solución
Entonces, podemos usar la siguiente URL para realizar la petición a /api/addAdmin
y modificar nuestra cuenta de usuario. Esto se conoce como Server-Side Request Forgery (SSRF), para obtener más información, se recomienda mirar HackTricks.
Flag
Finalmente, debemos iniciar sesión nuevamente para actualizar nuestro token de sesión y veremos la flag en la página principal:
HTB{s5rf_m4d3_m3_w3t_my_p4nts!}