0xAzoz@ubuntu:~$ โ–

/0xAzoz

Bug Bounty Hunter โ€ข Web Security Researcher

๐Ÿฌ HTB CTF: CandyVault โ€“ NoSQL Login Bypass

2025-05-14
NoSQLAuthenticationWebHTB

ุจุณู… ุงู„ู„ู‡ ุงู„ุฑุญู…ู† ุงู„ุฑุญูŠู…

HTB CTF: CandyVault โ€“ NoSQL Login Bypass

๐Ÿ“… Date: October 12, 2023
๐ŸŽฏ Difficulty: VERY EASY
๐Ÿ‘จโ€๐Ÿ’ป Author: 0xAzoz
๐Ÿ“ Category: Web, Authentication Bypass


Quick Summary:

This was a fun and clean NoSQL injection challenge the idea was simple: break the login form using MongoDB quirks and grab the flag. No need to overcomplicate it just understanding the logic behind the query was enough to break it.


Recon & First Look:

When I loaded the target, it was just a basic login page โ€” no register, nothing else to poke.

Looking at the code (they gave us the source code in ./challenge/application/app.py), I spotted how the login logic works. Here's the juicy part:

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

Focus on This Line:

user = users_collection.find_one({"email": email, "password": password})

We can see here that:


The Vulnerability:

In MongoDB, The find_one() function is used with a filter directly built from user-controlled input (email and password) without applying any sanitization or validation. This creates a classic NoSQL injection vulnerability.

So instead of passing:

{
  "email": "admin@vault.htb",
  "password": "password123"
}

We just passed:

{
  "email": { "$ne": 0 },
  "password": { "$ne": 0 }
}

The $ne operator is used to bypass the authentication process. With the following query, the user will be able to access the first account in the database, without needing to know any valid identifiers

That basically means:

โ€œGive me any user whose email is not 0 and password is not 0 Itโ€™s a logic operator, like != in SQL.โ€

MongoDB goes, โ€œsure,โ€ returns the first user in the DB (which of course not zero) โ€” and boom! Logged in.

I got redirected to candy.html with the flag printed at the top

Lessons:

References:


โ† Back to Home