MongoDB本身不防注入,db.eval()必须禁用;防注入关键是对每个用户输入字段做显式类型+结构双校验,并隔离$where、$regex、$expr等高危操作符。

直接说结论:MongoDB本身不防注入,db.eval() 必须禁用;防注入的关键不是换驱动,而是对每个用户输入字段做显式类型+结构双校验,且必须隔离 $where、$regex、$expr 等高危操作符。
为什么 typeof req.body.username !== 'string' 是第一道硬门槛
攻击者根本不需要懂 BSON,只要提交 {"$ne": null} 这种对象,就能让 findOne({ username: req.body.username }) 变成全量匹配。Mongoose 的 strict: true 对查询条件完全无效,原生驱动更不会拦截——它只负责把 JS 对象序列化发出去。
实操建议:
- 所有字符串类字段(
username、email、phone)必须先用typeof value === 'string'排除对象、数组、null、undefined - 再用正则限定内容:
/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$/比/^.+$/安全得多 -
value.trim() === ''要单独判断,空格、零宽字符、BOM 头都得拒掉 - 绝对不要
JSON.parse(req.body.xxx)—— 这会触发原型污染或正则 DoS,纯属给漏洞加戏
如何安全使用 $regex 而不打开注入后门
$regex 是 NoSQL 注入的“绿色通道”,攻击者填 .* 就能全表扫描,填 $(sleep(1000))(若 JS 未禁用)还能搞服务端延时。它不像 SQL 的 LIKE 那样有天然边界,一旦透传原始输入,前面所有校验都归零。
实操建议:
- 永远配
$options: 'i',避免大小写绕过 - 对
req.query.q做转义:req.query.q.replace(/[.*+?^${}()|[]\]/g, '\$&') - 禁止让用户控制
$options字段,比如{ $options: req.query.flags }是高危写法 - 性能上,只有
^abc这类前缀正则才可能走索引,.*abc默认全表扫
如何确认并彻底禁用 javascriptEnabled
db.eval() 在 MongoDB 4.0+ 默认关闭,但旧配置迁移常遗漏。它不是“可能被利用”,而是“只要连上数据库就能执行任意 JS”——db.eval("while(true){}") 能拖垮 CPU,db.eval("db.getSiblingDB('admin').runCommand({shutdown:1})") 能直接关库。
实操步骤:
- 进 shell 执行
db.runCommand({ getParameter: 1, javascriptEnabled: 1 }),返回{"javascriptEnabled": true}就说明还开着 - 编辑
/etc/mongod.conf,在security:下加一行:javascriptEnabled: false - 必须重启
mongod进程,重载配置无效 - 禁用后仍要检查
$function(4.4+ 引入),它不受javascriptEnabled控制,需单独用db.adminCommand({ setParameter: 1, disableJavaScript: true })
聚合管道里哪些地方最容易漏掉防御
聚合是 NoSQL 注入的重灾区,因为 $match、$sort、$project 都支持操作符,而开发者常误以为“用了 aggregate() 就比 find() 安全”。实际上,db.users.aggregate([{ $match: req.body.filter }]) 和 find(req.body.filter) 危险等级完全一样。
容易踩的坑:
-
$sort: { [req.query.field]: 1 }—— 字段名未白名单校验,可注入"$ne"或任意字段 -
$project: { name: 1, email: { $cond: [...] } }—— 若$cond分支来自用户输入,可能带入$function或$where - 前端传来的整个 pipeline 数组没做深度校验,攻击者可塞入额外
$lookup或$out阶段 - 真正难防的是“看似无害”的字段映射,比如
{ $addFields: { score: { $multiply: [req.body.weight, "$base"] } } }——req.body.weight若是{"$gt": 0},整个表达式就失控了
最常被忽略的一点:防御必须落在每个具体字段上,而不是“整个查询对象”。username 字段只接受字符串,page 字段只接受正整数,sort 字段只允许预设字段名——没有通用过滤函数,只有业务语义明确的校验逻辑。

















