NoSQL数据库虽然天生不像传统SQL数据库那样容易遭受经典的SQL注入攻击,但这并不意味着它是安全的。攻击者可以利用NoSQL查询操作符(如$where、$gt、$lt、$ne、$regex等)构造恶意查询,实现数据泄露、权限绕过甚至服务器端代码执行。最典型的案例就是MongoDB中通过$where操作符注入JavaScript代码,或者利用$regex操作符进行正则表达式拒绝服务攻击。核心解决思路只有一条:永远不要把用户输入直接拼接到查询条件中,必须使用参数化查询、输入白名单校验和操作符白名单限制三层防线。
很多开发者有一个误区,认为换了NoSQL就不用担心注入了。实际上,NoSQL注入的危害一点不比SQL注入小。MongoDB、Redis、CouchDB、Elasticsearch等主流NoSQL数据库都存在各自的注入向量。下面我会逐一拆解具体案例和对应的防御方案。
一、MongoDB $where操作符注入:最经典的NoSQL注入案例MongoDB的$where操作符允许在查询中执行任意JavaScript代码。如果开发者直接将用户输入嵌入$where条件,攻击者就能执行任意命令。比如下面这个存在漏洞的代码:
// 危险代码 - 直接拼接用户输入
app.get('/search', function(req, res) {
var username = req.query.username;
db.collection('users').find({
$where: "this.username == '" + username + "'"
}).toArray(function(err, results) {
res.json(results);
});
});
攻击者只需要传入这样的参数:username='; db.dropDatabase(); //,拼接后的查询就变成了执行删除整个数据库的JavaScript代码。更隐蔽的攻击是传入 username=' || this.password == '123456',直接绕过认证逻辑。
正确的处理方式是完全避免使用$where,改用标准查询操作符,并对输入进行严格校验:
// 安全代码 - 使用参数化查询和标准操作符
app.get('/search', function(req, res) {
var username = req.query.username;
// 白名单校验:只允许字母、数字、下划线
if (!/^[a-zA-Z0-9_]{1,50}$/.test(username)) {
return res.status(400).json({ error: 'Invalid username format' });
}
db.collection('users').find({
username: username // 直接作为值传入,MongoDB驱动会自动转义
}).toArray(function(err, results) {
res.json(results);
});
});
二、$regex操作符注入与正则表达式拒绝服务(ReDoS)
MongoDB支持$regex操作符进行模糊查询。攻击者可以构造特殊的正则表达式,让正则引擎陷入指数级回溯,导致CPU飙升、服务崩溃。比如传入类似 (a+)+$ 这样的恶意正则,在匹配长字符串时会造成灾难性的性能问题。
// 危险代码 - 用户直接控制正则
app.get('/search', function(req, res) {
var keyword = req.query.keyword;
db.collection('products').find({
name: { $regex: keyword, $options: 'i' }
}).toArray(function(err, results) {
res.json(results);
});
});
防御方法有两个层面。第一,限制正则表达式的复杂度,比如限制长度不超过50个字符,禁止出现嵌套量词。第二,设置查询超时时间,避免单个恶意查询拖垮整个服务:
// 安全代码 - 限制正则并设置超时
app.get('/search', function(req, res) {
var keyword = req.query.keyword;
// 限制长度和特殊字符
if (keyword.length > 50 || /[*+?{}()|[\]\\]/.test(keyword)) {
return res.status(400).json({ error: 'Invalid search pattern' });
}
// 转义特殊正则字符
var escapedKeyword = keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
db.collection('products').find({
name: { $regex: '^' + escapedKeyword + '$', $options: 'i' },
$maxTimeMS: 1000 // 1秒超时
}).toArray(function(err, results) {
res.json(results);
});
});
三、Redis命令注入:不只是查询,还能执行命令
Redis虽然是键值存储,但它支持Lua脚本执行。如果应用程序将用户输入拼接到EVAL或EVALSHA命令中,就会产生命令注入。典型漏洞代码如下:
// 危险代码 - Redis Lua脚本注入
var script = "return redis.call('get', '" + userInput + "')";
redisClient.eval(script, 0, function(err, result) {
// 处理结果
});
攻击者可以传入 userInput = "'); redis.call('flushall'); --" 来清空整个Redis数据库。解决方案是使用Redis的参数化调用方式,通过KEYS数组传递参数而不是字符串拼接:
// 安全代码 - 使用KEYS数组传参
var script = "return redis.call('get', KEYS[1])";
redisClient.eval(script, 1, userInput, function(err, result) {
// 处理结果
});
同时,生产环境必须禁用或重命名FLUSHALL、CONFIG、SHUTDOWN等危险命令,并开启Redis的认证机制。
四、Elasticsearch查询注入:DSL也能被利用Elasticsearch使用基于JSON的查询DSL。如果直接将用户输入拼接到query_string或script字段中,攻击者可以注入额外的查询逻辑。例如:
// 危险代码 - 直接拼接到query_string
var userQuery = req.query.q;
var body = {
"query": {
"query_string": {
"query": userQuery
}
}
};
攻击者传入 q=* AND access_level:admin 可以获取不该看到的管理数据。正确做法是使用Elasticsearch官方客户端提供的参数化查询构建器,而不是手动拼接JSON字符串:
// 安全代码 - 使用官方查询构建器
var esQuery = {
index: 'users',
body: {
query: {
bool: {
must: [
{ match: { name: userQuery } }
]
}
}
}
};
// 同时对userQuery做长度和字符限制
五、通用防御策略:三层防护体系
不管是哪种NoSQL数据库,防御注入攻击的核心策略都是相通的,我总结为三层防护体系。
第一层是输入验证。所有用户输入必须在进入查询逻辑之前经过白名单校验。不要试图用黑名单过滤,因为绕过方式太多。白名单只允许预期的字符类型和长度范围,比如用户名只允许字母数字,搜索关键词限制在合理长度内。
第二层是参数化查询。永远使用数据库驱动提供的参数化接口,而不是字符串拼接。MongoDB的Node.js驱动、Java驱动都会自动处理转义。Redis使用KEYS数组传参。Elasticsearch使用查询构建器对象。
第三层是最小权限和运行时防护。数据库账户只给必要的权限,不要用root账户跑应用。设置查询超时、连接数限制、操作频率限制。开启数据库的审计日志,监控异常查询模式。
六、容易被忽视的隐蔽攻击向量除了上面提到的主流注入方式,还有几个容易被忽视的点。一是NoSQL数据库的聚合管道(aggregation pipeline)如果接受用户输入控制$group、$project等阶段,同样可以注入恶意表达式。二是某些ORM框架(如Mongoose)虽然提供了一定保护,但如果使用了raw()方法或$where仍然会暴露风险。三是JSON注入本身,如果NoSQL数据库存储的是用户可控的JSON文档,反序列化时也可能触发代码执行。
开发者在代码审查时,要特别关注所有涉及用户输入的数据库操作点,逐一确认是否使用了参数化方式。自动化安全扫描工具如npm audit、Snyk等也应该集成到CI/CD流程中,及时发现依赖库中的已知漏洞。
总结来说,NoSQL注入的本质和SQL注入一样,都是信任了不该信任的用户输入。区别只在于注入的语法和目标不同。只要坚持"输入不可信、拼接不安全、参数化是底线"这三个原则,就能有效防御绝大多数NoSQL注入攻击。安全不是一次性的工作,而是需要持续审计、持续监控、持续更新防御策略的长期工程。
