Contents

🌐 No Sql Injection

A detailed write-up of the Web challenge 'No Sql Injection' from PicoCTF - 2024

/images/PicoGym/PicoCTF-2024/NoSQLInjection/challenge_presentation.png
Challenge Presentation

📊 Challenge Overview

Category Details Additional Info
🏆 Event PicoGym Event Link
🔰 Category Web 🌐
💎 Points Out of 500 total
⭐ Difficulty 🟡 Medium Personal Rating: 2/10
👤 Author NGIRIMANA Schadrack Profile
🎮 Solves (At the time of flag submission) 4.351 solve rate
📅 Date 10-02-2025 PicoGym
🦾 Solved By mH4ck3r0n3 Team:

📝 Challenge Information

Can you try to get access to this website to get the flag? You can download the source here

🎯 Challenge Files & Infrastructure

Provided Files

Files:

🔍 Initial Analysis

First Steps

Initially, the website appears as follows:

/images/PicoGym/PicoCTF-2024/NoSQLInjection/site_presentation.png
Site Presentation

With a login screen. Given the title of the challenge, I immediately thought of a NoSQL Injection. In fact, after opening the attached files, in the server.js file, I already found the first hint of NoSQL.

1
2
const mongoose = require("mongoose");
const { MongoMemoryServer } = require("mongodb-memory-server");

When MongoDB is used, it is always good practice to check for a NoSQL Injection. As we can see, the user is created with the fields email, firstName, lastName, password, token (where the token field contains the flag):

1
2
3
4
5
6
7
const userSchema = new mongoose.Schema({
  email: { type: String, required: true, unique: true },
  firstName: { type: String, required: true },
  lastName: { type: String, required: true },
  password: { type: String, required: true },
  token: { type: String, required: false, default: "{{Flag}}" },
});

All set with type: String. I also found the initialization of a first user:

1
2
3
4
5
6
   const initialUser = new User({
      firstName: "pico",
      lastName: "player",
      email: "picoplayer355@picoctf.org",
      password: crypto.randomBytes(16).toString("hex").slice(0, 16),
    });

which shows an email I can use to log in: picoplayer355@picoctf.org (so the only field I don’t know is the password for the user pico). In the source of the login page, I found a js script that makes a request to /login, sending the email and password fields that we enter in the form with 'Content-Type': 'application/json'. If the response is handled successfully, I am redirected to the /admin page:

/images/PicoGym/PicoCTF-2024/NoSQLInjection/page_source.png
Page Source

Trying to open BurpSuite and intercepting the login request with email=a and password=a, I get this:

/images/PicoGym/PicoCTF-2024/NoSQLInjection/intercept.png
Intercept

Continuing to review the attached files, these are the exact lines of code where the vulnerability is present:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
      const user = await User.findOne({
          email:
            email.startsWith("{") && email.endsWith("}")
              ? JSON.parse(email)
              : email,
          password:
            password.startsWith("{") && password.endsWith("}")
              ? JSON.parse(password)
              : password,
        });

If email or password start and end with {}, the server attempts to parse them as JSON objects using JSON.parse. This allows the attacker to send malformed or malicious queries, which are accepted by the database as valid. Once the vulnerability is understood, we can move on to exploitation.

🔬 Vulnerability Analysis

Potential Vulnerabilities

  • NoSQL Injection

🎯 Solution Path

Exploitation Steps

Initial setup

Since we already have a valid email picoplayer355@picoctf.org, we only need to perform a NoSQL Injection on the password field.

Exploitation

In MongoDB, a query like the one used in the backend tries to find a user that satisfies two conditions:

  • Condition on the email: email = picoplayer355@picoctf.org:

    • This condition is already satisfied, since we know a valid user with that email.
  • Condition on the password: password = something:

    • This is the point we need to bypass. If we don’t know the correct password, normally the query would fail.

Since it’s a logical AND, both conditions must be true to find a user in the database. However, by exploiting the unvalidated input, we can manipulate the password condition to always be true. Thanks to the vulnerability, we can use MongoDB’s $ne operator. This operator means “not equal” or “different from”. For example:

1
"password": { "$ne": "qualcosa" }

It states: “Find a document where the password is different from ‘something’”. Since there is always a password that is different from 'a' (or any other static value we provide), this condition will always be true, regardless of the actual content of the password field. By doing so, we can exploit the injection to retrieve the user data and the flag.
However, there is an important detail to consider: as we can see from the vulnerable code, the function password.startsWith("{") is called. If we directly send a JSON object as the password value, for example:

1
{"email": "picoplayer355@picoctf.org", "password": {"$ne": "a"}}

The backend will try to call the startsWith function on password. Here’s the problem: the value of password is no longer a string, but already a JSON object (in our example: {"$ne": "a"}). Since JSON objects in JavaScript don’t have the startsWith() method (which is a function defined only for strings), an error occurs, and the server returns:

1
{"success": false, "error": "password.startsWith is not a function"}

/images/PicoGym/PicoCTF-2024/NoSQLInjection/bad_request.png
Bad Request

So, since it expects a string, we need to send a string. But be careful here as well, because if we send:

/images/PicoGym/PicoCTF-2024/NoSQLInjection/bad_char.png
Bad Char

We will get another error due to the escaping of the ' character. So, I use the \ character to escape the quotes, and by sending the following payload, I obtain the flag encoded in base64 contained in the token:

1
{"email": "picoplayer355@picoctf.org", "password": {\"$ne\": \"a\"}}

/images/PicoGym/PicoCTF-2024/NoSQLInjection/injection.png
Injection

Once the base64 flag is extracted, we simply decode it to obtain the plain text:

1
echo;echo cGljb0NURntqQmhEMnk3WG9OelB2XzFZeFM5RXc1cUwwdUk2cGFzcWxfaW5qZWN0aW9uXzc4NGU0MGU4fQ== | base64 -d

(I added echo; at the beginning to have a \n before the output, so the flag appears on the next line and not attached to the command text).

Flag capture

/images/PicoGym/PicoCTF-2024/NoSQLInjection/manual_flag.png
Manual Flag

🛠️ Exploitation Process

Approach

The automatic exploit leverages the previously seen NoSQL Injection and performs all the steps mentioned, i.e., it retrieves the token, decodes it from base64, and then prints only the flag.

🚩 Flag Capture

Flag

Proof of Execution

/images/PicoGym/PicoCTF-2024/NoSQLInjection/automated_flag.png
Automated Flag
Screenshot of successful exploitation

🔧 Tools Used

Tool Purpose
Python Exploit
Burp Suite Web Testing

💡 Key Learnings

Time Optimization

When MongoDB is present, always keep in mind that it could be a NoSQL Injection challenge.

Skills Improved

  • Binary Exploitation
  • Reverse Engineering
  • Web Exploitation
  • Cryptography
  • Forensics
  • OSINT
  • Miscellaneous

📚 References & Resources

Learning Resources


📊 Final Statistics

Metric Value Notes
Time to Solve 00:02 From start to flag
Global Ranking (At the time of flag submission) Challenge ranking
Points Earned Team contribution

Created: 10-02-2025 • Last Modified: 10-02-2025 *Author: mH4ck3r0n3 • Team: *