很多人觉得NoSQL数据库灵活方便,特别是在处理海量数据时,不像传统关系型数据库那样受表结构限制。但在实际使用中,尤其是做多条件查询时,一不小心就会留下安全隐患。
别让查询变“后门”
比如你在做一个用户管理系统,想根据用户名和角色查找特定管理员。写了个查询条件:
{
"username": { "$regex": ".*admin.*" },
"role": "admin"
}看起来没问题,但正则匹配如果没做输入过滤,攻击者只要在用户名里输入特殊字符,就可能绕过规则查到不该看的数据。更危险的是,有些NoSQL支持嵌套操作符,像 $ne(不等于)、$gt(大于),一旦拼接字符串时不加验证,查询逻辑就可能被篡改。
动态查询别图省事
开发时为了方便,有人喜欢直接把前端传来的筛选参数拼成查询对象。比如用户选了“状态=启用”和“创建时间>2024-01-01”,代码就这么写:
let query = {
status: req.query.status,
createdAt: { "$gt": req.query.startTime }
};
User.find(query);问题出在哪?前端传的值完全不可信。攻击者可以传一个 { "$ne": "" } 当作 status,结果就是查出所有状态非空的用户,包括停用和锁定的。这在权限管理严格的系统里,简直是灾难。
白名单+类型校验才是正路
解决办法其实不复杂。对每个可查字段,先定义允许的操作类型。比如状态只能是“active”或“disabled”,创建时间只能是合法日期格式。写个简单的校验函数:
function buildQuery(params) {
let query = {};
if (params.status === "active" || params.status === "disabled") {
query.status = params.status;
}
if (params.startTime && isValidDate(params.startTime)) {
query.createdAt = { "$gt": new Date(params.startTime) };
}
return query;
}这样哪怕前端传了乱七八糟的东西,后端也只认白名单里的规则。既保证了功能灵活性,又堵住了漏洞。
还有种常见场景:商品搜索。用户可以组合筛选价格区间、品牌、评分。如果直接把JSON丢给数据库,攻击者完全可以塞进 $where 或 $expr 这类能执行脚本的字段,轻则拖慢服务,重则拿到服务器权限。这类高级操作符,非必要绝不要开放给外部请求。
日志里也能藏雷
别忘了,查询语句有时会打到日志里。如果日志没脱敏,像密码、身份证号这种敏感字段的查询条件可能就被明文记录了。运维一导出日志分析性能,数据就泄露了。建议在日志输出前,自动过滤掉包含 $eq、$in 等关键词的敏感字段。
NoSQL的灵活性是把双刃剑。用得好,系统响应快、扩展性强;用得糙,一个查询就能让整个数据防线崩塌。多花几分钟做参数校验,比事后应急便宜多了。