MongoDB原生驱动和Mongoose均不提供SQL式参数化查询,防注入关键在于对用户输入字段显式校验与结构过滤;必须严格限制类型、格式及操作符使用,否则任何查询方法都可能被绕过。

直接说结论:MongoDB原生驱动和Mongoose都不提供类似SQL预编译的“参数化查询”机制,所谓“用官方驱动就能防注入”是常见误解;真正起作用的是你如何构造查询对象——必须对每个用户输入字段做显式类型校验与结构过滤,而不是依赖驱动自动处理。
为什么官方驱动不等于自动防注入
MongoDB驱动(如Node.js的mongodb>)只负责把JavaScript对象序列化为BSON发给服务端,它不会、也不能判断你传进去的{ $ne: "" }是不是恶意构造的。只要查询对象里混入了用户原始输入且未加约束,findOne()、find()、updateOne()全都会照单执行。
常见错误现象:db.users.findOne({ username: req.body.username }) 中,攻击者提交{"$ne": null}导致绕过登录校验
- 所有主流语言驱动(Java、Python、Node.js)行为一致:不解析、不拦截、不重写查询对象
- Mongoose 6.0+ 默认启用严格模式(
strict: true),但它只限制schema外字段写入,对查询条件完全不干预 - 别信“用了Mongoose就安全了”——
Model.find(req.body)这种写法在任何版本下都高危
对字符串字段必须做类型 + 格式双校验
username、email、title这类字段,业务上本就该是纯字符串,那就得用代码把它钉死,不能让对象、数组、null溜进来。
实操建议:
- 先用
typeof value === 'string'排除非字符串类型 - 再用正则限定内容范围,例如
/^[a-zA-Z0-9_]{3,20}$/比/^.+$/安全得多 - 空字符串、前后空白、Unicode控制字符要主动拒绝,
value.trim() === ''需单独判断 - 禁止使用
JSON.parse()二次解析用户输入——这可能触发原型污染或正则DoS,纯属画蛇添足
示例:
if (typeof req.body.username !== 'string' || !/^[a-zA-Z0-9_]{3,20}$/.test(req.body.username.trim())) {
return res.status(400).json({ error: 'Invalid username' });
}
$regex、$where、$expr这些操作符必须隔离用户输入
它们是NoSQL注入的“特权通道”,哪怕前面做了类型校验,一旦放开这些操作符接收原始输入,整个防御就失效。
常见错误现象:{ name: { $regex: req.query.q } },攻击者填.*或$(sleep(1000))(若启用了JS执行)
-
$regex必须配合$options: 'i',且对req.query.q做转义:req.query.q.replace(/[.*+?^${}()|[]\]/g, '\$&') -
$where应彻底禁用——它会禁用索引,还允许执行任意JS,生产环境不该存在 -
$expr若必须用,只接受白名单内的操作符组合,req.body.age绝不能直接进{$expr: {$gt: ['$age', req.body.age]}} - 性能影响:
$regex不以^开头基本走不了索引;$where强制全表扫描
真正难防的是“合法但恶意”的BSON结构
比如{"$ne": {"$type": "string"}}这种嵌套操作符,它通过了typeof === 'object'检查,也符合JSON语法,但语义上已脱离业务预期。这时候光靠基础类型判断不够,得结合字段语义做深度校验——username字段根本不该允许出现$type操作符。
最容易被忽略的一点:后端路由层往往只校验body顶层结构,却放任嵌套字段(如req.body.filter.category)直通查询;而攻击者最常从这种“深层路径”下手。











