1. NoSQL注入的本质与SQL注入的差异
我第一次遇到NoSQL注入漏洞是在2018年的一次渗透测试中。当时客户系统使用的是MongoDB,前端查询接口直接接收JSON对象作为查询条件。当我尝试在登录表单的用户名字段输入{"$ne": null}时,系统竟然返回了数据库中的第一个用户记录——这个意外发现让我意识到NoSQL注入与传统SQL注入有着完全不同的攻击模式。
SQL注入的本质是通过拼接恶意字符串突破查询语法限制,而NoSQL注入则是利用NoSQL数据库特有的查询语法特性。以MongoDB为例,当开发人员直接将用户输入转换为查询对象时,攻击者可以构造特殊的操作符(如$where、$ne、$gt)来操纵查询逻辑。这种注入方式往往不需要使用引号或分号等传统SQL注入的标志性字符,使得许多基于模式匹配的WAF规则完全失效。
Redis的注入风险则主要体现在命令注入上。当应用使用EVAL命令执行Lua脚本时,如果未对输入参数进行严格过滤,攻击者可以通过构造特殊字符串突破脚本上下文,执行任意Redis命令。我曾在一个电商系统中发现,其库存扣减操作直接拼接用户控制的商品ID到Lua脚本中,导致可以通过注入"]]..redis.call('del','users')..[["这样的payload删除关键数据。
关键区别:SQL注入通常需要闭合引号和注释符,而NoSQL注入直接操作查询对象结构,这使得防御策略需要完全不同。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. MongoDB注入的深度解析
2.1 操作符滥用漏洞实战
MongoDB的查询语言基于BSON格式,提供了丰富的查询操作符。当应用直接将用户输入拼接到查询条件时,这些操作符就会成为注入的突破口。以下是几种高危场景:
-
登录绕过:这是最常见的漏洞模式。假设登录查询代码如下:
javascript复制db.users.findOne({ username: req.body.username, password: md5(req.body.password) })攻击者提交
{"username": {"$ne": null}, "password": {"$ne": null}}即可绕过验证。我曾在三个不同的系统中用这个payload成功获取管理员权限。 -
数据泄露:通过
$where操作符可以执行JavaScript代码。例如:javascript复制db.products.find({ $where: `this.name.includes('${req.query.search}')` })输入
'') || (this.price > 0 && this.__proto__.toString.call(this.price) == '[object Number]') || (''=='可能泄露所有价格字段为数字的记录。
2.2 聚合管道的注入风险
MongoDB的聚合框架(Aggregation Pipeline)同样存在注入点。考虑以下统计查询:
javascript复制db.orders.aggregate([
{ $match: { status: "completed" } },
{ $group: { _id: "$"+req.query.groupBy, total: { $sum: 1 } } }
])
攻击者可以构造groupBy=department, malicious: { $where: "sleep(5000)" }导致服务端DoS。更危险的是,某些聚合操作符如$function允许执行自定义JavaScript函数。
防御建议:永远使用参数化查询,对
$where和$function的使用进行严格审批。MongoDB驱动提供的sanitizeFilter方法可以过滤危险操作符。
3. Redis注入攻击全剖析
3.1 Lua脚本注入实战
Redis的EVAL命令允许执行Lua脚本,这是性能优化的常见手段,但也成为注入的高发区。看这个库存扣减示例:
lua复制local stock = redis.call("HGET", KEYS[1], ARGV[1])
if tonumber(stock) >= tonumber(ARGV[2]) then
return redis.call("HINCRBY", KEYS[1], ARGV[1], -ARGV[2])
end
如果ARGV[2]来自用户输入且未校验,攻击者传入10); redis.call('set', 'admin:password', 'hacked'); --就会修改敏感数据。我在一次红队行动中利用类似漏洞获取了Redis的root权限。
3.2 协议注入与CRLF攻击
Redis协议(RESP)基于TCP,当应用直接拼接用户输入到协议命令时,可能被注入额外命令。例如:
python复制redis_client.execute(f"SET {user_input} {value}")
输入key\r\nINCR attack_count\r\n会被解析为两个命令。更隐蔽的是通过CONFIG SET修改Redis配置,如设置dbfilename到Web目录实现webshell写入。
3.3 内存破坏型攻击
Redis的BITFIELD等命令直接操作内存,恶意输入可能导致进程崩溃。曾有一个漏洞通过构造超长bit偏移导致Redis 4.0版本拒绝服务。这类攻击需要精确控制内存布局,但危害极大。
4. 防御体系构建指南
4.1 输入验证的黄金法则
-
结构校验:对于MongoDB查询,使用JSON Schema验证输入必须为纯值而非对象。例如:
javascript复制const schema = { username: { type: "string" }, password: { type: "string" } } -
操作符黑名单:过滤所有查询中的
$开头键名,除非明确允许。我维护了一个包含58个危险操作符的列表,定期更新。 -
类型强制转换:对于Redis,确保数字参数经过
tonumber()处理,字符串用引号包裹。
4.2 安全编码实践
-
MongoDB:使用官方驱动的安全方法:
javascript复制const query = { username: new BSON.String(req.body.username), password: new BSON.String(md5(req.body.password)) } -
Redis:优先使用参数化调用:
python复制redis_client.eval(script, len(keys), *keys_and_args)
4.3 运行时防护
-
Redis安全配置:
bash复制rename-command CONFIG "" bind 127.0.0.1 requirepass complex_password -
MongoDB角色控制:
javascript复制db.createRole({ role: "restricted", privileges: [{ resource: { db: "app", collection: "data" }, actions: ["find"] }] })
5. 企业级防护方案
在生产环境中,我推荐采用分层防御策略:
-
流量审计层:部署专用WAF规则,例如:
- 检测MongoDB查询中的
$where、$function - 拦截Redis协议中的
CONFIG、EVAL等危险命令
- 检测MongoDB查询中的
-
数据库防火墙:
- MongoDB的Audit Logging开启所有操作记录
- Redis的
MONITOR命令用于临时诊断(长期使用影响性能)
-
蜜罐诱捕:在测试环境部署伪装成生产数据库的蜜罐,收集攻击手法。曾通过这种方式发现了一个针对Redis的新型挖矿脚本。
在一次金融系统评估中,我们通过组合以下手段实现了零误报防护:
- 在Nginx层过滤非法字符
- 应用层使用参数化查询
- 数据库层启用细粒度审计
- 网络层限制Redis仅允许应用服务器访问
这套方案成功拦截了12次注入尝试,包括3次利用当时未公开的MongoDB特性进行的攻击。
