← 返回题目列表

什么是 JavaScript 原型污染?前端如何防范?

高频 困难 第 16 / 26 题 更新于 2026/07/29
原型污染Prototype PollutionJavaScript前端安全

简化版

原型污染是攻击者通过 __proto__constructor.prototype 等特殊路径修改对象原型,影响后续所有继承该原型的对象。前端常见于深合并、解析 query、按路径赋值等工具函数,防护要过滤危险 key、使用安全合并、创建无原型对象并避免信任继承属性。

详细版

危险示例:

function setByPath(obj, path, value) {
  const keys = path.split('.')
  let cur = obj
  for (let i = 0; i < keys.length - 1; i += 1) {
    cur = cur[keys[i]] ||= {}
  }
  cur[keys[keys.length - 1]] = value
}

setByPath({}, '__proto__.isAdmin', true)
console.log({}.isAdmin) // true

防护重点是禁止写入 __proto__prototypeconstructor 等危险键;读取配置时使用 Object.hasOwn;深拷贝和 merge 使用成熟库并及时升级;对 JSON、URL 参数、表单字段等用户可控路径做白名单校验。

完整版教学

一、为什么原型会被污染

JavaScript 对象通过原型链查找属性。普通对象找不到自有属性时,会继续到 Object.prototype 上查。如果攻击者能把恶意属性写到公共原型,很多看似干净的新对象都会“继承”这份污染。

user = {}
  -> user.__proto__ === Object.prototype
  -> Object.prototype.isAdmin = true
  -> user.isAdmin 读取到 true

易错点:污染的可怕之处不在某一个对象被改了,而是后续大量对象的属性查找结果都被影响。

二、典型入口:深合并和路径赋值

function merge(target, source) {
  for (const key in source) {
    if (typeof source[key] === 'object' && source[key] !== null) {
      target[key] ||= {}
      merge(target[key], source[key])
    } else {
      target[key] = source[key]
    }
  }
  return target
}

merge({}, JSON.parse('{"__proto__":{"polluted":true}}'))
console.log({}.polluted) // 可能变成 true

很多漏洞不是直接写 Object.prototype,而是工具函数递归处理用户输入时误入危险路径。query 解析器、表单生成器、配置合并器、国际化路径设置都可能成为入口。

三、污染后如何变成真实攻击

原型污染本身只是改变属性查找,真正危害来自后续代码把这些属性当成安全决策或 DOM 参数。

const options = {}
if (options.isAdmin) {
  showAdminPanel()
}

假设 Object.prototype.isAdmin = true,上面逻辑就会误判。客户端还可能出现 DOM XSS gadget,例如某组件读取 options.innerHTML 并写入 DOM。污染 + gadget 组合,才会形成完整攻击链。

四、过滤危险 key

const BLOCKED_KEYS = new Set(['__proto__', 'prototype', 'constructor'])

function safeSet(target, key, value) {
  if (BLOCKED_KEYS.has(key)) return
  target[key] = value
}

过滤要覆盖每一级路径,而不只是第一层。constructor.prototype.xa.__proto__.x 都可能危险。更严格的业务应采用白名单字段,只允许已知配置项进入合并逻辑。

五、使用无原型对象和自有属性检查

const dict = Object.create(null)
dict.safe = true

Object.hasOwn(dict, 'safe') // true

Object.create(null) 创建的对象没有 Object.prototype,适合作字典表。读取安全敏感字段时,不要直接用 if (obj.isAdmin),应检查自有属性并设置明确默认值。

写法风险
if (config.enabled)可能读取继承属性
Object.hasOwn(config, 'enabled')只看自有属性
for...in会遍历可枚举继承属性
Object.keys()只返回自有可枚举属性

六、依赖库和运行时防护

历史上不少深拷贝、merge、querystring 库都出现过原型污染漏洞。前端项目应固定锁文件、及时升级依赖,并对安全公告敏感。对于不可信 JSON,不要随意深合并到全局配置。

某些运行时支持禁用或限制 __proto__ 行为,但前端不能假设所有浏览器都有同样能力。工程上更稳的是:用户输入白名单、危险 key 黑名单、安全工具函数和依赖审计一起做。

七、常见误区与追问

  • 误区:原型污染只影响 Node.js。 客户端 JavaScript 同样有原型链和 DOM gadget 风险。
  • 误区:只过滤第一层 __proto__ 就够了。 嵌套路径和 constructor.prototype 也要处理。
  • 误区:污染后一定立刻 RCE。 前端更多是权限绕过、DOM XSS、逻辑污染,危害取决于 gadget。
  • 追问:为什么 for...in 危险? 它会遍历可枚举继承属性,可能把污染属性当作正常输入。
  • 追问:如何判断属性是自己的? 使用 Object.hasOwn(obj, key) 或安全的 hasOwnProperty.call
  • 追问:对象字典为什么可用 Object.create(null) 它没有原型链,减少继承属性干扰。

八、加强记忆

原型污染可以按“入口、污染、利用、防护”串起来:入口是深合并、路径赋值和 query 解析;污染是写入 __proto__constructor.prototype;利用依赖后续代码读取继承属性或危险 DOM 参数;防护靠危险 key 过滤、字段白名单、自有属性检查、无原型字典和依赖升级。它考的是你对 JS 原型链和安全边界的共同理解。