Ir al contenido

CandyVault

Autor
Santiago Chavarro

Análisis
#

La pagina lo único que tiene es un login, si tratamos de ingresar por ejemplo probando admin:admin no nos funcionara nada.

Revisando el código nos damos cuenta de que esta usando una base de datos MongoDB:

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"]

Además tenemos un archivo llamado migration.py en el cual podemos ver que los correos y las contraseñas se generan aleatoriamente por lo que no pueden ser credenciales debiles.

@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("/")

Este código al usar la entrada del usuario directamente es vulnerable a hacer una inyección NoSQL, un ejemplo del código seguro sería así:

from flask import request, render_template, redirect
from werkzeug.security import check_password_hash
import re

def is_valid_email(email):
    return re.match(r"[^@]+@[^@]+\.[^@]+", email) is not None

def login():
    email = request.form.get('email')
    password = request.form.get('password')

    if not isinstance(email, str) or not is_valid_email(email):
        return redirect("/")
    if not isinstance(password, str) or len(password) == 0:
        return redirect("/")

    user = users_collection.find_one({"email": email})
    if user and check_password_hash(user['password_hash'], password):
        return render_template("candy.html", flag=open("flag.txt").read())
    else:
        return redirect("/")

Exploit
#

En este punto cometí un error en su momento no me fije y no me di cuenta que admitía el formato json por lo que lo estaba haciendo con el url-encode por lo que las payloads que estaba pasando no estaban funcionando al final termine cambiando el formato y usando las payloads para json quedando de la siguiente forma:

Referencias
#