核心是切断污染路径——通过源头过滤敏感键名(如__proto__、constructor、prototype)、使用object.create(null)替代{}、加固深拷贝与动态赋值、冻结原型链及schema校验,全面防御原型污染。

核心是切断污染路径——不让用户可控的键名(尤其是 __proto__、constructor、prototype)写入原型链。
从源头过滤敏感键名
所有接收外部输入的地方都要做校验,不只是顶层字段,还要检查嵌套路径(比如 "a.__proto__.b" 或 "user.settings.constructor"):
- 用正则递归扫描每个键名和路径段:
/\b(__proto__|constructor|prototype)\b/i - JSON 解析后、合并前立即过滤:删除或报错处理含敏感词的键
- 对 URL 参数、表单数据、配置文件等统一加一层“净化中间件”
改用无原型对象替代 {}
凡用于存储用户数据、路由参数、插件注册表、缓存映射等场景,都别再用 {}:
- 直接改用
Object.create(null)创建干净容器 - 它没有
__proto__,也不继承toString、hasOwnProperty等方法 - 需要调用原生方法时,显式借用:
Object.prototype.hasOwnProperty.call(obj, key)
避免不安全的对象操作
深拷贝、合并、动态赋值这些高危操作必须加固:
- 禁用
lodash.merge、_.set处理不可信输入;升级到 lodash ≥ 4.17.21 - 手动实现合并逻辑时,跳过敏感键名,不递归处理
__proto__路径 - 用
Object.assign()替代深合并(仅浅层,更可控);现代环境优先用structuredClone() - 动态设置属性时(如
obj[path] = value),先校验path是否合法
运行时加固(高安全要求场景)
服务启动后冻结基础原型,一劳永逸阻止新增属性:
-
Object.freeze(Object.prototype)—— 注意:该操作不可逆,需确认无第三方库依赖原型扩展 - 搭配 TypeScript 或 Zod/Joi 做 schema 校验,非法字段直接拒绝,不静默忽略
- 测试时主动注入 payload:
{"__proto__": {"test": 1}},再执行console.log(({}).test),输出undefined才算有效











