flawopen.com/原型污染/JSON.parse()
JSON.parse() 会导致原型污染吗?
不会。JSON.parse() 会创建名为 __proto__ 的自有数据属性;解析过程不会调用继承的 setter,也不会改变原型。只有后续代码通过 setter 复制该键、递归合并不可信键,或写入攻击者可控的对象路径时,才可能产生风险。
// JSON.parse 创建数据;不会污染原型。 const input = '{"__proto__":{"isAdmin":true}}'; const parsed = JSON.parse(input); // Object.assign 会对目标执行 Set。此处只会改变 // 这个局部演示对象的原型。 const copied = Object.assign({}, parsed); Object.getPrototypeOf(copied).isAdmin; // true Object.prototype.isAdmin; // undefined
// 解析后只构造应用明确接受的字段。 const input = '{"theme":"dark","locale":"en","__proto__":{"isAdmin":true}}'; const parsed = JSON.parse(input); const config = { theme: parsed.theme, locale: parsed.locale, }; // 不可信字典键应避免触发继承的 setter。 const options = Object.assign(Object.create(null), parsed);
为什么 JSON.parse() 不是污染源
JSON 键与对象字面量中的原型 setter 是两种不同操作。JSON 解析会在新值上定义自有属性,不会执行 obj.__proto__ = value。后续操作可能不同:Object.assign(target, source) 会向目标执行赋值,因此自有的 __proto__ 键可能触发目标继承的 setter。这会改变目标对象的原型;单凭这一点并不会修改 Object.prototype。
执行 const o = JSON.parse('{"__proto__":{"a":1}}') 后,Object.hasOwn(o, "__proto__") 为真,而 Object.prototype.a 仍未定义。后续复制到普通目标时,目标的原型可能改变。对象展开会创建自有属性而不调用 setter,但不能防护不安全的递归合并或路径写入。
真正的风险出现在哪里
解析后的 JSON 是不可信数据,并非已污染的原型。风险取决于后续写入点:递归合并、路径 setter,或向普通对象赋值的复制循环。易受攻击的递归合并可能沿着 __proto__ 或 constructor.prototype 写入共享原型;单层复制则可能只改变目标对象。判断影响前应追踪具体辅助函数和目标对象。
审查不可信键写入的位置,而不只是 JSON 解析位置:深度合并函数、对象路径 setter,以及执行赋值的 for...in 循环。用户可控键应拒绝 __proto__、constructor 和 prototype;优先按已验证的结构重建对象,或使用无原型字典。
常见问题
如果应用不需要该键,reviver 可以在解析时将其删除。这属于纵深防御,不能替代对不安全合并和路径写入的修复;constructor.prototype 和非 JSON 输入也需要考虑。
请求体解析器只会生成 JavaScript 值,并不会让后续复制或合并自动变安全。请检查实际使用的解析器和后续操作。有些查询字符串解析器还会根据方括号语法构建嵌套对象,这是另一条输入路径。
不会。序列化只读取值并生成 JSON 文本,不会改变对象原型。如果文本随后被解析并传入不安全的处理函数,风险才可能再次出现。