跳到正文
>_ITDITDWeb 安全平台

安全指南

Node.js 中的原型污染:运行时不负责的部分,以及如何在自己的代码中防御

传入数据里的一个特殊键,经过递归合并,会让你从未设置过的属性出现在不相关的对象上。Node.js 表示这不属于其核心的漏洞,所以防御是你自己的责任。

发布于 2026-09-05 更新于 2026-10-04 最后核实 2026-09-05 3 分钟阅读

你接收了外部数据,之后你从未设置过的属性开始出现在不相关的对象上。或者 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种结果)。

在自己的代码中检查什么

1

是否把外部输入当作深层对象接收?

请求体、查询字符串、表单、Webhook 载荷——找出所有接收整个嵌套结构并合并进现有状态的地方。外部输入只从这里进来。用 Schema 明确决定接收哪些键,是最快的修法。

2

改变字典类对象的创建方式

凡是当作“键在运行时决定的容器”来用的对象(以 ID 为键的映射、一组设置项),都用 Object.create(null) 创建。没有模板,就没有东西可继承。能用结构解决的,就不必靠验证去解决。

3

统一用 Object.hasOwn 做存在性检查

搜索那些依赖属性是否存在的分支,并逐一改写。权限、标志和选项的检查优先——它们直接关系到授权的实现方式。

4

让工具盯住依赖

你自己没写递归合并,只要依赖写了就一样——所以文档举的例子里有第三方库。依赖中的已知漏洞靠人工跟不上,请像 osv-scanner 入门那样交给工具持续监控。被投毒的依赖带来的更大风险,见 防御 npm 供应链攻击。

5

用同样的眼光检查服务端框架的入口

每个 Node 框架都有反序列化请求体的路径。我们查看 NVD 数据时发现,Next.js 的漏洞集中在服务端的职责上——缓存、中间件、Server Actions(Next.js 和 React 的漏洞多吗?)。先放下**“这是前端库,跟这个无关”**的想法。

本站的看法:责任划分写明的地方,要自己检查

本文在运维上最有用的,不是对策清单,而是那句“这不被视为 Node.js 核心的漏洞”。责任划分写得这么清楚的地方,范围之外的部分就没有人管:不会有 CVE,也不会有更新来修。

我们把它看作与以下问题同一类型:证书挡不住接管(子域名接管)、拦截设置不会删除底下已有的策略(公开的存储桶)。**你以为有人负责的地方,实际上是谁在负责?**认真回答一次,优先顺序往往会大幅改变。

出处(一手资料)

  • Node.js「Security Best Practices」(Prototype Pollution Attacks / CWE-1321 一节)— nodejs.org(威胁模型上的立场、两种结果、所举的 CVE 和7项对策都来自这份文档;2026年9月5日确认)

接下来阅读

FAQ

Q原型污染是 Node.js 的 bug 吗?更新能修好吗?
A

不会有更新来修。Node.js 官方安全指南明确写道:按照 Node.js 的威胁模型,依赖攻击者控制用户输入的原型污染不被视为 Node.js 核心的漏洞,因为 Node.js 信任应用代码提供的输入。紧接着又写道:尽管如此,原型污染对 Node.js 应用和第三方库来说是一类严重的漏洞,应在应用和依赖层面实施防御。按设计,这是你的责任。

Q实际会出什么问题?
A

两种结果。第一,你从未设置过的属性开始出现在整个进程的对象上——如果权限标志或选项只靠“是否存在”来判断,注入的值就会被当真,权限判断或业务逻辑就会被扭曲。第二,拒绝服务:内置原型被破坏后,hasOwnProperty 之类的标准方法会抛出“is not a function”错误,让与输入毫无关系的代码崩溃。第二种结果说明这不只是权限提升的问题。

Q应该在哪里修?
A

在入口和数据结构上。在入口,对外部和不可信的请求做 JSON Schema 验证,让意外的键根本进不来。在结构上,停止使用不安全的递归合并,用 Object.create(null) 创建字典类对象,让它们根本没有原型。读取时用 Object.hasOwn(obj, key) 确认属性是对象自身的,并避免依赖 Object.prototype 上的方法。运维层面还有 Object.freeze 和 Node 的 --disable-proto 选项。

Q我从没写过递归合并,这样就安全了吗?
A

不安全。文档举的例子既有 Node.js 本身(CVE-2022-21824),也有第三方库(Lodash,CVE-2018-3721)。配置合并、查询字符串解析、表单反序列化,都是常用库替你组装深层对象的地方。所以防御并不止于你自己的代码,还要配合用工具持续监控依赖。