Engineering · Jul 2, 2026 · 7 min read

NoSQL injection: error signatures differ by database, so probe per engine

MongoDB, CouchDB, Redis and DynamoDB each produce distinct responses when injection succeeds - a scanner keyed on one engine is a false-negative factory for the rest.

NoSQL databases share no standard query language, which means they share no standard error format either. A scanner tuned to MongoDB's operator syntax is a false-negative factory the moment it points at CouchDB, Redis or DynamoDB. The fix is not a bigger regex - it is probing each engine on its own terms and confirming the result the way that engine actually reveals it.

01No shared query language, no shared signature

MongoDB query operators like $where, $gt and $regex are syntactically valid JSON, so they sail straight past a WAF looking for SQL keywords. But the tell that the injection worked is completely different from one store to the next, and a probe that only knows one of them reports a clean bill of health for the other three.

MongoDB ──► operator eval flips a boolean differential: auth response changes CouchDB ──► Mango selector parse error textual: {"error":"query_parse_error"} Redis ──► RESP framing / newline fault looks like connection noise DynamoDB ──► ValidationException on operand service error, not app error
Four engines, four unrelated confirming signals. One regex cannot see all of them.

02The confirming artifact is differential, not a string

For MongoDB operator injection the proof is behavioural, not textual. Send a greater-than operator where the app expects a string and the query changes meaning from "username equals X" to "username greater than the empty string" - which is true for every stored user. If the endpoint that rejected a normal wrong password now returns a session, the boundary broke, and the difference between the two responses is the evidence:

login-probe.httphttp
# baseline: a normal wrong-credential attempt
POST /api/login  {"username":"alice", "password":"nope"}
HTTP/1.1 401 Unauthorized

# operator injection: same endpoint, JSON operators instead of strings
POST /api/login  {"username":{"$gt":""}, "password":{"$gt":""}}
HTTP/1.1 200 OK   Set-Cookie: session=...   # boundary failed

Nothing destructive runs. The payload reads data the query was always able to read; it just proves the operator was interpreted rather than treated as a literal.

Proven - CriticalMongoDB operator injection - authentication bypass - /api/login
POST /api/login {"username":{"$gt":""},"password":{"$gt":""}} ──► 200 OK Set-Cookie: session=... (valid session for a forged pair) baseline string login ──► 401. Differential holds across 5 repeats.
Oracle: differential. No records written or read beyond the auth check. Severity critical - a forged credential pair yields a session.

03Probe per engine or miss most of the surface

Because the confirming signal differs, the engine has to fingerprint the store first and then apply the matching oracle. The signatures below are maintained per engine, not merged into one pattern - merging is exactly what creates the silent gap.

EngineInjection vectorConfirming signal
MongoDBJSON query operators ($gt, $ne, $where, $regex)Differential: auth or result set changes with the operator
CouchDBMango selector fields in the request bodyTextual: query_parse_error with the reflected selector
RedisRESP protocol framing via unescaped delimitersNewline-delimiter fault; distinguish from real connection noise
DynamoDBExpression operands in filter/key conditionsValidationException from the service, not the app layer

04Severity follows confirmed capability

An operator injection that returns a session for a forged credential pair is critical - authentication bypass, demonstrated, not inferred. The same class against a search filter that only widens a result set is a medium until you show it crosses a tenant boundary. We never rate on reachability alone; the confirmed capability sets the number.

4
engines, four distinct oracles
0
records modified during the probe
1
differential, repeated to rule out flukes

The takeaway is the same one that runs through the whole engine: a finding is a fact about the response, proven on the engine in front of you, not a keyword match borrowed from a different database.

Evidence over heuristicsReachable beats foundMonitor → Block: rolling out a gate without slowing teamsIaC-grounded threat modelingAir-gapped AppSec with no phone-homeOne platform vs three tools
See it on your own app →