Luke a Pro

Luke Sun

Developer & Marketer

šŸ‡ŗšŸ‡¦

NoSQL Injection

| , 4 minutes reading.

1. Definition

NoSQL Injection occurs when an application accepts user input and passes it directly into a NoSQL database query (like MongoDB, CouchDB, or Cassandra) without validation.

Unlike SQLi which uses string concatenation, NoSQLi often exploits Object Injection. If an attacker can inject a JSON object where a string was expected, they can alter the query logic using database operators (e.g., $ne, $gt, $where).

2. Technical Explanation

Consider a Node.js/Express app checking a login:

// VULNERABLE CODE
db.collection('users').findOne({
  username: req.body.username,
  password: req.body.password
}, callback);

The developer expects req.body.password to be a string. However, body parsers (like body-parser) handle JSON. An attacker can send:

{
  "username": "admin",
  "password": { "$ne": null }
}

The query becomes: ā€œFind a user where username is ā€˜admin’ AND password is NOT NULL.ā€ Since the admin has a password, this condition evaluates to true, bypassing authentication without knowing the password.

3. Attack Flow (Auth Bypass)

sequenceDiagram
    participant Attacker
    participant API as Node.js App
    participant DB as MongoDB

    Attacker->>API: POST /login (JSON Body)
    Note right of Attacker: { "username": "admin", "password": {"$ne": ""} }

    API->>DB: db.users.findOne({username: "admin", password: {$ne: ""}})
    
    Note over DB: Query Operator '$ne' (Not Equal) is executed.
    Note over DB: Logic: Does admin's password != ""? YES.
    
    DB-->>API: Returns Admin User Object
    API-->>Attacker: 200 OK (Auth Token)

4. Real-World Case Study: Rocket.Chat (CVE-2021-22911)

Target: Rocket.Chat (Open Source Chat Platform). Vulnerability Class: Blind NoSQL Injection.

The Vulnerability: A password reset feature did not properly sanitize the user-provided token before querying the database. Users.findOne({ "services.password.reset.token": token })

The Attack:

  1. Attackers could use the $regex operator to brute-force the reset token character by character.
  2. By sending { "$regex": "^A" }, they could check if the token started with ā€œAā€.
  3. If the server responded differently (e.g., ā€œToken validā€ vs ā€œToken invalidā€), they confirmed the character.
  4. This allowed attackers to extract the valid reset token and takeover any administrator account.

Impact: Critical privilege escalation allowing full server compromise.

5. Detailed Defense Strategies

A. Strict Input Type Validation

Ensure input is of the expected primitive type (String, Number) before passing it to the database.

  • Mechanism: Reject the request if typeof req.body.password !== 'string'.
  • Libraries: Use schema validation libraries like Joi, Zod, or class-validator.
// Defense Example
const { username, password } = req.body;
if (typeof password !== 'string') {
  return res.status(400).send('Invalid Input');
}

B. Use ODMs with Strict Schema Validation

Using Object Data Modeling (ODM) libraries like Mongoose helps, but it is not a ā€œmagicā€ fix.

  • Risk: Mongoose query filters can still accept MongoDB operators in some contexts unless explicitly sanitized or typed.
  • Defense: Use a strict schema where fields are explicitly typed (e.g., password: String). However, you should always cast the user input to a string before passing it to the query to ensure no nested objects/operators are processed.
// Defense Example
const username = String(req.body.username);
const password = String(req.body.password);
User.findOne({ username, password });

C. Sanitize Input Keys

Remove keys starting with $ from user input to prevent operator injection.

  • Tools: mongo-sanitize npm package.

    const sanitize = require('mongo-sanitize');
    const cleanUser = sanitize(req.body.username);

D. Least Privilege

Limit the database user’s permissions. Do not allow Javascript execution (disable $where, mapReduce, and group commands in MongoDB config) unless absolutely necessary.

6. References