GridFS的filename触发路径穿越是因为MongoDB驱动不校验其合法性,仅作普通字符串存储,若应用层将其拼接本地路径、URL或命令则直接导致漏洞;必须入库前用白名单严格限制为ASCII字母、数字、下划线、短横线、点且长度≤255。

filename字段本身不参与路径解析,但一旦被应用层用于拼接本地路径、构造 URL 或执行命令,就立刻变成路径穿越高危入口——必须在入库前彻底剥离危险语义。
为什么 GridFS 的 filename 会触发路径穿越?
MongoDB 驱动对 filename 完全不做校验,fs.files.insertOne({ filename: "../../../../etc/passwd" }) 能成功写入,只是把它当普通字符串存。真正出问题的环节永远在后续:比如用这个值拼 /var/uploads/ + filename 去读文件,或拼 http://internal-api/?file= + filename 去请求内部服务。
filename 入库前必须做的白名单校验
不是“过滤掉 ../”,而是只允许明确安全的字符集——这是唯一能堵死绕过手段的做法。
- 只允许 ASCII 字母、数字、下划线
_、短横线-、点. - 长度严格限制 ≤ 255 字符(MongoDB 本身不限,但业务必须拦)
- 禁止任何 Unicode 控制字符、零宽空格、路径分隔符
/\:?*">| - 正则建议:
^[a-zA-Z0-9_.-]{1,255}$,匹配失败直接拒收
绝对不能把 filename 当路径或 URL 片段用
常见错误是拿 filename 直接拼接:exec("cp " + filename)、fetch("/api/fetch?name=" + filename)、res.redirect("/download/" + filename)。这些操作和 GridFS 无关,但攻击者只要控制 filename 就能链式打穿。
- 读写本地文件?用
_id查fs.files,再通过GridFSBucket.openDownloadStream()流式读取,别碰filename - 要转发给下游服务?建映射表:查出
_id后,查 Redis 或 DB 中file_id → internal_url的关联,不拼字符串 - 响应头或重定向中禁用原始
filename,尤其Content-Disposition: attachment; filename=...和Location头
上传时用 GridFSBucket.uploadFromStream() 而非手动插集合
直接 db.fs.files.insertOne() 绕过所有校验钩子,连 chunk 大小都不检查,等于把门敞开。
- 必须走
bucket.uploadFromStream(),它会在内存中预读并校验元信息 - 显式设置
chunkSizeBytes: 262144(256KB),避免小 chunk 拖慢性能或大 chunk 卡住上传 - 传入
options.metadata存权限字段,别把逻辑塞进filename
最麻烦的不是写校验逻辑,而是确保所有代码路径——包括日志记录、错误提示、后台任务、迁移脚本——都遵守同一套白名单规则。一个被忽略的 console.log(filename) 可能泄露原始值,成为 SSRF 链路起点。











