NoSQL Injection
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:
- Attackers could use the
$regexoperator to brute-force the reset token character by character. - By sending
{ "$regex": "^A" }, they could check if the token started with āAā. - If the server responded differently (e.g., āToken validā vs āToken invalidā), they confirmed the character.
- 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-sanitizenpm 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.
