你接收了外部数据,之后你从未设置过的属性开始出现在不相关的对象上。或者 hasOwnProperty 这样的标准方法突然变成“不是函数”,应用崩溃了。两者都是原型污染的症状。
Node.js 负责什么,留给你的是什么
Node.js 明确表示这不是它的漏洞
Node.js 官方安全指南写得很明确:“按照 Node.js 的威胁模型,依赖攻击者控制用户输入的原型污染不被视为 Node.js 核心的漏洞,因为 Node.js 信任应用代码提供的输入。”
紧接着:“尽管如此,原型污染对 Node.js 应用和第三方库来说是一类严重的漏洞,应在应用和依赖层面实施防御。”
这并不矛盾,而是说明了责任在哪里。运行时站在不保证输入格式正确的一侧。也就是说,“等 CVE、等更新”在这里行不通。这又是本站反复遇到的一种情况:你以为有人在管的部分,其实没有人负责。
会出什么问题(两种结果)
外部输入(JSON、查询字符串、表单)
带有特殊键
↓ 递归合并 / 深拷贝 / 组装配置
共享的模板(原型)被改写
↓ 继承它的所有对象也随之改变
1. 出现你从未设置的值
权限标志和选项检查被扭曲
2. 内置方法消失
应用在不相关的地方崩溃(DoS)
- 1. 属性注入
- 一个“不可能被设置”的值变得可以读取。凡是选项对象或权限标志只靠是否存在来判断的地方,注入的值都会被直接当真。影响到权限判断就成了权限问题;影响到模板或命令的组装,后果可能严重得多
- 2. 拒绝服务
- 模板本身被破坏后,
hasOwnProperty之类的内置方法会抛出“is not a function”错误。文档把这列为潜在的 DoS。值得记住的一点:这不只是权限提升的问题 - 影响范围
- 文档举的例子是 Node.js 本身(CVE-2022-21824)和第三方库(Lodash,CVE-2018-3721)。即使你自己没写递归合并,只要依赖写了,就一样
文档列出的对策(7项)
Node.js 把对策逐条列了出来。按作用分组更容易落实:在入口挡住、让结构无法被污染、改变读取方式。
在入口挡住
・避免不安全的递归合并(文档举了 CVE-2018-16487)
・对外部和不可信的请求实施 JSON Schema 验证
让意外的键根本进不来是最可靠的办法。重新考虑那些接收整个深层对象再合并进现有状态的设计。
改变结构和读取方式
・用 Object.create(null) 创建没有原型的对象(最适合做字典)
・用 Object.freeze(MyObject.prototype) 冻结原型
・用 Node 的 --disable-proto 选项禁用 Object.prototype.proto
・用 Object.hasOwn(obj, key) 确认属性是对象自身的
・避免使用 Object.prototype 上的方法
收益最大的改动是存在性检查的写法
实际上覆盖面最广的一项是改用 Object.hasOwn(obj, key)。普通的存在性检查(这个属性存在吗?)对从模板继承来的值也会回答“存在”,注入的值正是这样被读到的。确认属性是对象自身的,单这一点就能防住第1种结果的大部分。
出于同样的理由,避免直接在对象上调用 obj.hasOwnProperty(key)——这个方法本身就是污染可能抹掉的东西之一(第2种结果)。
在自己的代码中检查什么
是否把外部输入当作深层对象接收?
请求体、查询字符串、表单、Webhook 载荷——找出所有接收整个嵌套结构并合并进现有状态的地方。外部输入只从这里进来。用 Schema 明确决定接收哪些键,是最快的修法。
改变字典类对象的创建方式
凡是当作“键在运行时决定的容器”来用的对象(以 ID 为键的映射、一组设置项),都用 Object.create(null) 创建。没有模板,就没有东西可继承。能用结构解决的,就不必靠验证去解决。
统一用 Object.hasOwn 做存在性检查
搜索那些依赖属性是否存在的分支,并逐一改写。权限、标志和选项的检查优先——它们直接关系到授权的实现方式。
让工具盯住依赖
你自己没写递归合并,只要依赖写了就一样——所以文档举的例子里有第三方库。依赖中的已知漏洞靠人工跟不上,请像 osv-scanner 入门那样交给工具持续监控。被投毒的依赖带来的更大风险,见 防御 npm 供应链攻击。
用同样的眼光检查服务端框架的入口
每个 Node 框架都有反序列化请求体的路径。我们查看 NVD 数据时发现,Next.js 的漏洞集中在服务端的职责上——缓存、中间件、Server Actions(Next.js 和 React 的漏洞多吗?)。先放下**“这是前端库,跟这个无关”**的想法。
出处(一手资料)
- Node.js「Security Best Practices」(Prototype Pollution Attacks / CWE-1321 一节)— nodejs.org(威胁模型上的立场、两种结果、所举的 CVE 和7项对策都来自这份文档;2026年9月5日确认)
接下来阅读
- 同类问题:不安全的反序列化(由外部数据决定类型)
- 依赖:osv-scanner 入门 / 防御 npm 供应链攻击
- 全景:Next.js 和 React 的漏洞多吗?(服务端会出现什么)
- 设计:认证与授权的区别(让注入的值永远到不了权限判断)
FAQ
Q原型污染是 Node.js 的 bug 吗?更新能修好吗?
不会有更新来修。Node.js 官方安全指南明确写道:按照 Node.js 的威胁模型,依赖攻击者控制用户输入的原型污染不被视为 Node.js 核心的漏洞,因为 Node.js 信任应用代码提供的输入。紧接着又写道:尽管如此,原型污染对 Node.js 应用和第三方库来说是一类严重的漏洞,应在应用和依赖层面实施防御。按设计,这是你的责任。
Q实际会出什么问题?
两种结果。第一,你从未设置过的属性开始出现在整个进程的对象上——如果权限标志或选项只靠“是否存在”来判断,注入的值就会被当真,权限判断或业务逻辑就会被扭曲。第二,拒绝服务:内置原型被破坏后,hasOwnProperty 之类的标准方法会抛出“is not a function”错误,让与输入毫无关系的代码崩溃。第二种结果说明这不只是权限提升的问题。
Q应该在哪里修?
在入口和数据结构上。在入口,对外部和不可信的请求做 JSON Schema 验证,让意外的键根本进不来。在结构上,停止使用不安全的递归合并,用 Object.create(null) 创建字典类对象,让它们根本没有原型。读取时用 Object.hasOwn(obj, key) 确认属性是对象自身的,并避免依赖 Object.prototype 上的方法。运维层面还有 Object.freeze 和 Node 的 --disable-proto 选项。
Q我从没写过递归合并,这样就安全了吗?
不安全。文档举的例子既有 Node.js 本身(CVE-2022-21824),也有第三方库(Lodash,CVE-2018-3721)。配置合并、查询字符串解析、表单反序列化,都是常用库替你组装深层对象的地方。所以防御并不止于你自己的代码,还要配合用工具持续监控依赖。