深拷贝本身不安全,是原型污染攻击的常见入口;防御需从数据进入业务逻辑的第一刻开始,覆盖解析、克隆、合并全过程,必须递归清洗所有嵌套路径中的__proto__、constructor、prototype等敏感键,并优先选用structuredClone()、klona/src/json.js或destr等经安全验证的方案。

深拷贝本身不是安全操作,而是原型污染攻击的常见入口。关键不在于“要不要深拷贝”,而在于“怎么拷贝才不会把恶意键写进原型链”。防御必须从数据进入业务逻辑的第一刻开始,贯穿解析、克隆、合并全过程。
输入清洗必须递归且覆盖嵌套路径
JSON 解析后不能只删顶层 __proto__,攻击者常用 {"a": {"__proto__": {"x": 1}}} 或 {"x.y.__proto__.z": "v"} 绕过简单校验。必须对每个 key 和完整路径字符串做正则匹配:
- 使用
/\b(__proto__|constructor|prototype)\b/i扫描所有键名和路径片段 - 清洗动作要放在
JSON.parse()后第一行代码,不能等到合并或赋值时才处理 - 避免
if (key === '__proto__') continue这类仅比对字面量的写法,它对点号路径完全无效
优先选用原生或经过安全验证的克隆方案
自己手写深拷贝极易遗漏防护细节,生产环境应直接采用已验证的安全实现:
-
structuredClone()(Node.js 17+ / 现代浏览器):原生 API,自动忽略不可序列化属性和敏感键,不走原型链 -
klona/src/json.js:仅 28 行,专为 JSON 安全类型设计,显式防御__proto__ -
destr:解析 + 克隆一体化,默认过滤原型污染键,比JSON.parse更稳妥 - 避免使用
lodash.merge或旧版_.cloneDeep(
对象创建层切断污染源头
即使输入清洗到位,若目标容器仍继承 Object.prototype,污染就可能在后续操作中被间接触发。因此:
- 凡用于存储用户数据、配置映射、路由参数、插件注册表等场景,统一用
Object.create(null)初始化 - 它没有
__proto__,也不继承toString、hasOwnProperty等方法,天然免疫污染 - 需改用
Object.prototype.hasOwnProperty.call(obj, key)替代obj.hasOwnProperty(key)
运行时加一层兜底防护
静态防御可能被新型绕过方式击穿,上线后仍需动态加固:
- 服务启动时执行
Object.freeze(Object.prototype),阻止新增/修改属性(注意兼容性,建议先在沙箱环境验证) - Node.js 启动参数添加
--disable-proto=throw,强制拦截对__proto__的非法访问 - 在日志中对
req.body、JSON.parse、fs.readFileSync等入口点添加字段扫描,命中敏感键立即告警

















