news 2026/4/15 19:12:09

React 背锅了?一行恶意 JSON 就能让你的 Node.js 服务器瞬间宕机!

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
React 背锅了?一行恶意 JSON 就能让你的 Node.js 服务器瞬间宕机!

近期,Node.js 官方发布了一系列重要的安全更新,修复了 8 个安全漏洞。这次更新涉及 Node.js 20.x、22.x、24.x 和 25.x 等所有活跃版本,影响范围之广,几乎覆盖了所有使用 React Server Components、Next.js 或 APM 监控工具的生产环境应用。

问题到底出在哪里?

在 Node.js 的异步编程世界里,有一个叫async_hooks的底层 API。它的作用是追踪异步操作的生命周期。听起来很技术化,但你可能每天都在用它,只是不知道而已。

React Server Components 用它来追踪渲染上下文,Next.js 用它来追踪请求信息,Datadog、New Relic 等 APM 工具用它来追踪请求链路。可以说,async_hooks已经成为现代 Node.js 应用的基础设施。

但问题就出在这里。当你的代码出现深层递归,导致栈溢出时,正常情况下 Node.js 会抛出一个RangeError: Maximum call stack size exceeded错误,你的try-catch可以捕获它,你的错误处理器可以记录它,然后应用继续运行。

但如果启用了async_hooks,情况就完全不同了。当栈溢出发生时,Node.js 会直接以退出代码 7 终止进程,不经过try-catch,不触发uncaughtException处理器,就这样,应用死了。

为什么这么容易被触发?

需要明确的是,这个 BUG 的触发需要满足几个条件:

  1. 启用了async_hooks(使用 AsyncLocalStorage、APM 工具等)

  2. 代码中存在深度递归

  3. 递归过程中创建了 Promise(触发 async hooks)

  4. 递归深度达到栈溢出的程度

虽然听起来条件挺多,但在实际应用中,这些条件很容易同时满足。让我举个实际的例子。

假设你有一个 Next.js API 路由,用来处理用户上传的 JSON 数据:

export defaultasyncfunction handler(req, res) { try { const data = req.body; const result = processNestedData(data); res.json({ success: true, result }); } catch (err) { // 你以为这里能捕获错误?天真了 console.error('Processing failed:', err); res.status(500).json({ error: 'Processing failed' }); } } function processNestedData(data) { if (Array.isArray(data)) { return data.map(item => processNestedData(item)); } return transform(data); }

一切看起来都很安全,对吧?有try-catch,有错误处理。

但如果有人发送一个嵌套了几千层的 JSON 数组,你的服务器会直接崩溃。不是返回 500 错误,而是整个进程退出,所有正在处理的其他请求都会中断。

这就是一个典型的 DoS(拒绝服务)攻击向量。

技术原因是什么?

问题的根源在于async_hooks的实现方式。当你创建一个 Promise 时,V8 引擎会同步调用 promise hook,这个 hook 会触发 Node.js 的async_hooks回调。

这意味着,每次new Promise()都会在当前调用栈上添加额外的栈帧。当你的代码递归创建 Promise 时,栈上既有用户代码的帧,也有async_hooks的帧。

当栈最终溢出时,抛出错误的那一刻,执行上下文正好在async_hooks的回调里。Node.js 为了避免在 hook 内部出现错误导致的不一致状态,会用一个特殊的错误处理器TryCatchScope::kFatal来包裹这些回调。

kFatal的意思是:如果这里出错了,状态已经不可恢复,直接退出进程。

虽然这个设计的初衷是为了保护应用,但在栈溢出这个场景下,它反而成了问题。因为错误本身来自用户代码,而不是 hook 本身。

Node.js 是怎么修复的?

这次的修复方案很巧妙。Node.js 在TryCatchScope的析构函数里增加了一个检测:如果捕获到的是栈溢出错误,就把它重新抛给用户代码,而不是当作致命错误处理。

TryCatchScope::~TryCatchScope() { if (HasCaught() && mode_ == CatchMode::kFatal) { Local<Value> exception = Exception(); // 检测到栈溢出?重新抛出而不是退出 if (IsStackOverflowError(env_->isolate(), exception)) { ReThrow(); Reset(); return; } // 其他致命错误:按原逻辑退出 FatalException(/* ... */); } }

这样一来,栈溢出错误就能像正常情况下一样被try-catch捕获了。

为什么说这只是缓解措施?

Node.js 官方在博客中特别强调:这只是一个缓解措施(mitigation),而不是根本性的解决方案。

原因很简单:栈溢出的行为本身就不是 ECMAScript 规范的一部分。JavaScript 规范假设栈空间是无限的,没有规定引擎应该在栈溢出时做什么。

抛出可捕获的RangeError只是 V8 等引擎的「尽力而为」行为。依赖这种未定义的行为来保证服务可用性,本身就是有风险的。

正确的做法是:如果你的代码可能处理深度不确定的递归结构(比如用户上传的 JSON),应该主动限制递归深度,或者用迭代算法替代递归。

function processNestedData(data, maxDepth = 100) { function process(item, depth) { if (depth > maxDepth) { thrownewError('Nesting too deep'); } if (Array.isArray(item)) { return item.map(child => process(child, depth + 1)); } return transform(item); } return process(data, 0); }

不要指望运行时帮你兜底。

还有哪些漏洞被修复?

除了这个栈溢出问题(CVE-2025-59466),这次更新还修复了其他几个重要漏洞:

CVE-2025-55131(高危):Buffer 分配时的竞态条件可能导致未初始化的内存泄露,从而暴露敏感信息如 token、密码等。

CVE-2025-55130(高危):通过精心构造的符号链接路径可以绕过文件系统权限模型,读写任意文件。

CVE-2025-59465(高危):发送畸形的 HTTP/2 HEADERS 帧可以让服务器崩溃。

CVE-2026-21636(中危):权限模型可以被 Unix Domain Socket 绕过,访问本地特权服务。

CVE-2026-21637(中危):TLS PSK/ALPN 回调中的异常可能导致进程崩溃或文件描述符泄露。

CVE-2025-59464(中危):处理 TLS 客户端证书时的内存泄漏。

CVE-2025-55132(低危)fs.futimes()可以绕过只读权限修改文件时间戳。

这些漏洞涵盖了内存安全、权限模型、网络协议等多个层面,影响范围确实很广。

哪些版本受影响?

好消息是,Node.js 团队已经为所有活跃版本发布了补丁:

  • Node.js 25.3.0(当前版本)

  • Node.js 24.13.0(LTS)

  • Node.js 22.22.0(LTS)

  • Node.js 20.20.0(LTS)

如果你还在使用更老的版本(18.x 及以下),这些版本已经停止维护,不会收到安全补丁。如果无法升级,可以考虑联系 OpenJS 基金会的商业支持。

特别需要注意的是,如果你在使用 Node.js 24 或更新版本,React 和 Next.js 应用不会受到栈溢出问题的影响,因为AsyncLocalStorage在这些版本中已经用 V8 的新 APIAsyncContextFrame重新实现,不再依赖async_hooks

但是,如果你的 APM 工具直接使用了async_hooks.createHook(),所有版本仍然受影响。


如果你正在生产环境使用 Node.js,特别是:

  • 使用了 React Server Components

  • 使用了 Next.js

  • 使用了任何 APM 工具(Datadog、New Relic、Dynatrace、Elastic APM、OpenTelemetry 等)

  • 使用了AsyncLocalStorage

你应该尽快升级到上述的补丁版本。

同时,检查你的代码中是否有处理不可信输入的递归逻辑,加上深度限制或改用迭代算法。

写在最后

这次漏洞揭示了一个有趣的现象:我们每天依赖的基础设施,可能建立在一些未被明确保证的行为之上。

async_hooks从一个调试 API 发展成了整个生态系统的关键依赖。React、Next.js、所有主流 APM 工具,都在用它。但它的某些边界情况,却从未被充分测试和规范化。

这不是某个框架或工具的问题,而是整个生态系统演化过程中的自然结果。当一个 API 变得足够流行,它的每一个实现细节都可能成为事实标准。

好在 Node.js 团队及时发现并修复了这个问题。但更重要的是,这提醒我们:不要假设运行时会永远按照你期望的方式工作,特别是在处理边界情况时。

防御性编程,永远不过时。

一个有趣的社区讨论

这次漏洞披露后,在开发者社区引发了一些有趣的讨论。

有开发者在推特上评论说:"Next.js 真是被诅咒了,拜托别再试图重新发明 PHP 了,天哪"(next.js is cursed tbh, just stop trying to reinvent php please omg)。

Node.js 核心维护者 Matteo Collina 则直接指出:"核心问题出在 React。"(The "core" problem is in React.)

这个对话很有意思。很多人把矛头指向 Next.js,但技术上说,问题的根源确实在 React Server Components 对AsyncLocalStorage的使用。Next.js 只是在此基础上构建的框架。

这也提醒我们,在讨论技术问题时,准确定位问题的层次很重要。表面上看起来是某个框架的问题,实际上可能是更底层的设计决策带来的影响。

当然,这并不是说 React 或 Next.js 做错了什么。它们使用AsyncLocalStorage是合理的选择,只是碰上了 Node.js 实现中的一个边界情况。技术栈的复杂性就在于此:每一层都在合理地使用下一层的 API,但层与层之间的交互可能产生意想不到的问题。


参考资料:

  • Node.js 官方博客 - Mitigating Denial-of-Service Vulnerability

  • Node.js 2026 年 1 月安全发布公告

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 13:43:49

AI不是阶层跨越的通天绳,也不会塑造新寒门

我最近在思考一个问题&#xff1a;女儿上学后让不让她用AI辅助学习&#xff1f;刚好看到由阿里千问举办的一场线下圆桌会议&#xff0c;会议的主题就是——《孩子到底能不能用AI》。 AI与教育的深度融合&#xff0c;是不一场不可逆的迁徙。 我们无法拒绝电视、广播、互联网、智…

作者头像 李华
网站建设 2026/4/16 13:32:34

Qwen-Image-Layered更新了!支持更多层数灵活拆分

Qwen-Image-Layered更新了&#xff01;支持更多层数灵活拆分 1. 简介 最近&#xff0c;Qwen-Image-Layered 模型迎来一次重要升级——现在支持更灵活的图层数量设置&#xff0c;能够根据图像复杂度和编辑需求动态调整分解层数。这项更新让图像的“可编辑性”迈上新台阶。 你…

作者头像 李华
网站建设 2026/4/16 13:44:06

YOLO26 predict参数怎么设?source输入详解教程

YOLO26 predict参数怎么设&#xff1f;source输入详解教程 最新 YOLO26 官方版训练与推理镜像 本镜像基于 YOLO26 官方代码库 构建&#xff0c;预装了完整的深度学习开发环境&#xff0c;集成了训练、推理及评估所需的所有依赖&#xff0c;开箱即用。 1. 镜像环境说明 核心框…

作者头像 李华
网站建设 2026/4/16 13:01:35

PyTorch通用开发实战案例:微调ResNet全流程部署指南

PyTorch通用开发实战案例&#xff1a;微调ResNet全流程部署指南 1. 引言&#xff1a;为什么选择这个环境做ResNet微调&#xff1f; 你是不是也经历过这样的场景&#xff1a;每次开始一个新项目&#xff0c;都要花半天时间配环境、装依赖、解决版本冲突&#xff1f;尤其是用Py…

作者头像 李华
网站建设 2026/4/13 10:34:12

Qwen3-0.6B图像描述缓存策略,节省计算资源

Qwen3-0.6B图像描述缓存策略&#xff0c;节省计算资源 1. 引言&#xff1a;为什么需要图像描述缓存&#xff1f; 你有没有遇到过这种情况&#xff1a;系统里有成千上万张图片&#xff0c;每次用户访问都要重新生成一遍描述&#xff1f;明明昨天刚生成过的图&#xff0c;今天打…

作者头像 李华
网站建设 2026/3/31 5:00:41

IQuest-Coder-V1 vs DeepSeek-Coder:复杂任务处理能力对比

IQuest-Coder-V1 vs DeepSeek-Coder&#xff1a;复杂任务处理能力对比 1. 为什么复杂任务处理能力正在成为代码模型的分水岭 你有没有遇到过这样的情况&#xff1a;写一个需要调用多个API、处理异常分支、还要兼顾性能优化的函数时&#xff0c;模型生成的代码总在第三层嵌套就…

作者头像 李华