深拷贝本身非漏洞,但处理不可信输入时易引发原型链污染;需审计高危调用点、替换为structuredClone或白名单+Object.assign、冻结关键原型、配置对象用Object.create(null)、输入必须白名单校验。

深拷贝本身不是漏洞,但不当使用深拷贝函数(尤其是处理不可信输入时)是原型链污染最常见、最危险的入口。安全审计中不能只看“用了深拷贝”,而要重点检查它是否在信任边界上被滥用。
识别高危深拷贝调用点
审计时应逐行排查所有可能接收外部数据的深拷贝操作,尤其关注以下模式:
-
直接解析并合并用户 JSON:如
_.merge({}, JSON.parse(userInput))或deepAssign(config, req.body),其中userInput或req.body未经过滤 -
使用已知存在风险的旧版工具函数:如
lodash < 4.17.21的_.merge、_.defaultsDeep;hoek < 5.0.3的Hoek.merge;或自研递归赋值函数未过滤__proto__、constructor、prototype及其大小写变体(如__PROTO__) - 配置加载逻辑中无条件 deep-extend:例如从 URL 参数、本地存储或第三方 API 获取配置后,未经白名单校验就合并进全局配置对象
用安全替代方案替换风险操作
不升级、不替换,仅靠“注意”无法通过审计。必须落地可验证的加固措施:
-
优先用
structuredClone:现代环境(Chrome 98+、Node.js 17+)中它是原生、安全、不走原型链的深拷贝方案,不会触发任何__proto__写入 -
降级时用白名单 +
Object.assign组合:对结构已知的对象,先用白名单过滤字段名,再用Object.assign({}, filteredInput)浅拷贝;若需深层,手动遍历并跳过敏感键 -
禁用危险库函数:在 ESLint 中启用
@typescript-eslint/no-unsafe-assignment和自定义规则,禁止在服务端或配置层调用_.merge等高危方法;CI 流程中加入npm audit和synk test扫描依赖树中的已知漏洞版本
运行时双保险:冻结 + 监控
即使代码层做了过滤,也要防止意外或绕过。审计应确认是否部署了底层防护:
立即学习“Java免费学习笔记(深入)”;
-
启动即冻结关键原型:在应用最顶部的 JS 文件(早于任何
node_modules脚本)执行:Object.freeze(Object.prototype);Object.freeze(Array.prototype);Object.freeze(Function.prototype);
注意:必须在第三方分析脚本、polyfill 前执行,否则无效 -
对配置对象使用
Object.create(null):如数据库连接配置、路由映射表等纯数据容器,直接创建无原型对象,天然免疫污染,比“防御性拷贝”更彻底 -
添加原型写入监控(开发/测试环境):重写
Object.prototype.__defineSetter__或用 Proxy 包裹Object.prototype,记录所有对admin、toString、hasOwnProperty等敏感属性的尝试写入,作为审计日志项
输入处理必须带白名单校验
深拷贝前的数据清洗,是审计必查项。不能只靠“黑名单过滤”,必须有明确的字段许可清单:
- 对每个接受外部输入的深拷贝场景,定义该输入的合法字段名集合(如用户注册接口只允许
name、email、avatar) - 解析 JSON 后,用
Object.keys(input).every(key => allowedKeys.includes(key))校验,不满足则拒绝整个请求 - 避免使用
JSON.parse(payload, (k, v) => k === '__proto__' ? undefined : v)这类简单替换——攻击者可用constructor.prototype或 Unicode 变体绕过


















