采用 Headless 架构并未消除攻击面。它只是转移了。2025-26 年的记录显示位置在哪里:React Server Components(CVE-2025-55182,未认证远程代码执行,发布于 2025 年 12 月 3 日)、Astro 的服务器岛屿和图像端点、Nuxt 有效负载缓存在 CDN 后面、npm 软件包,以及仍在运行 PHP 的 WordPress 后端。完全静态导出且没有服务器运行时确实会消除大部分框架级风险。不过,很多"静态"网站实际上并非如此。下面你会看到按框架划分的事件,然后是密钥、CSP、供应链、WordPress 源和边缘控制,最后是一个你可以适配到自己堆栈的加固表。
为什么"静态意味着无法被攻击"不再成立
直白地说:如果你的构建输出纯 HTML、CSS 和 JS 文件,由 CDN 提供服务,那么没有框架服务器可以攻击。没有 React Server Components 端点。没有服务器操作。没有中间件可绕过。这确实是一个很小的攻击面,也是为什么过去的 Jamstack 宣传有说服力的原因。
但看看 2026 年大多数 Next.js、Astro 和 Nuxt 项目实际上部署的东西。联系表单的服务器操作。检查 cookie 的中间件。一个图像优化端点。个性化标题的服务器岛屿。ISR 或按需重新验证。每一个都意味着一个进程在监听请求、解析输入并持有环境变量。这就是一个服务器。只是在你的代码库中它看起来不像一个。
所以问题不是"这个网站是 Headless 的吗?"而是"在请求时运行什么?"有三个诚实的答案:
- 没有。纯静态输出。你的风险在于构建管道和 CMS,而不是框架。
- 一个小的、已知的端点集合。图像优化、表单处理程序。修补它们、限制它们、对它们进行速率限制。
- 一个完整的应用程序运行时。SSR、服务器操作、身份认证、数据库访问。像对待任何 web 应用一样对待它,因为它就是一个。
我在其他地方已经做过与 WordPress 的架构比较(见 headless vs WordPress security),所以我不会重复。这篇文章是事件记录和对策。
Next.js:React2Shell、服务器操作和代理陷阱

React2Shell
CVE-2025-55182(React)和 CVE-2025-66478(Next.js)发布于 2025 年 12 月 3 日。严重级别。通过对 React Server Components 的精心构造请求进行未认证远程代码执行。根据 Vercel 的更新日志,受影响的 Next.js 版本是 >=14.3.0-canary.77、15.x 和 16.x 使用 App Router。修补后的 Next.js 发布版本是 15.0.5、15.1.9、15.2.6、15.3.6、15.4.8、15.5.7 和 16.0.7,React 在 19.0.1、19.1.2 和 19.2.1 中修复。
速度在这里很重要。Datadog Security Labs 在 2025 年 12 月 5 日前报告了公开利用代码和主动利用尝试。两天。如果你的补丁流程按月度节奏运行,那个间隙就是暴露的时间。
Vercel 提供 npx fix-react2shell-next 来更新受影响的版本。无论你采取哪条路线,都要在关联的公告中确认你的发布线的当前修复版本,因为后续的更新改变了目标。
12 月 11 日的后续更新
2025年12月11日,Next.js团队发布了另一个安全更新,涵盖两个额外的问题:
- CVE-2025-55184,拒绝服务(高)。App Router请求可能导致服务器挂起。第一个修复不完整;完整修复被跟踪为CVE-2025-67779。
- CVE-2025-55183,源代码泄露(中)。精心设计的请求可导致服务器函数返回其他服务器函数的编译源代码。其中硬编码的任何内容都会泄露。在运行时从环境读取的密钥不会。
Pages Router不受影响,没有解决方案。升级。修复版本包括14.2.35、15.5.9和16.0.10(查看advisory中适用于你的版本)。
server-actions的教训很直白:服务器操作是一个有更好调用约定的公共HTTP端点。将其参数视为恶意输入。在其中进行授权。永远不要在服务器函数可以访问的文件中将密钥写成字符串字面量。
中间件不是授权边界
CVE-2025-29927,在2025年3月21日披露(CVSS 9.1),允许精心设计的x-middleware-subrequest请求头完全跳过中间件。如果你唯一的身份验证检查在中间件中,它就会被绕过。Datadog的分析列出了12.3.5、13.5.9、14.2.25和15.2.3中的修复。Vercel托管的应用在基础设施层面受到保护。自托管应用在打补丁前都暴露了。
Next.js 16将middleware.ts重命名为proxy.ts,并默认在Node.js运行时上运行它;middleware.ts已弃用。我认为这个重命名是坦诚的。代理坐在应用前面。它可以重定向、重写、添加请求头。但它不应该是唯一阻止陌生人访问你数据的东西。
真正检查应该放在哪里:
- 在数据层,紧挨着返回行数据的查询。
- 在每个改变数据的服务器操作中。
- 在路由处理器中,按路由。
- 在代理中仅作为已登出用户的便利重定向,永远不要作为锁。
如果移除proxy.ts会暴露数据,那么你的授权逻辑放错地方了。
Astro:服务器岛屿、图片端点和中间件路径
Astro的默认是静态的,这是一个优势。但三个2025的advisory涉及添加运行时的部分。
服务器岛屿XSS。CVE-2025-64764,发布于2025年11月19日,高危(CVSS 7.1)。通过服务器岛屿端点的slots参数的反射型XSS。它影响任何使用服务器岛屿的应用,即使岛屿组件不使用slots。5.15.6及更早版本受影响;5.15.8修复了它。详情见GitHub advisory。
图片端点SSRF。CVE-2025-58179影响了@astrojs/cloudflare适配器(11.0.3至12.6.5)。在output为'server'且使用默认编译图片服务时,图片端点没有验证URL,导致了服务器端请求伪造。SSRF很危险,因为服务器从网络内部发起请求,可能会到达元数据服务或内部API。
中间件路径绕过。Astro 5.15.8还修复了使用双编码URL的路径规范化绕过(CVE-2025-64765)。与Next.js请求头技巧相同的错误家族:依赖URL看起来是特定方式的授权。
在决定是否受影响前,检查withastro/astro安全advisory中的确切版本范围。我不会猜测我指不出来的边界。
Astro 6和内置CSP
Astro 6在security.csp下提供了CSP API。根据Astro 6测试版公告,它对打包脚本和样式进行哈希(默认SHA-256),在构建和预览中工作,但在开发模式中不工作。最后一点会让人踩坑:在astro dev下看起来正常的页面在生产构建中应用策略时可能会破坏。总是要测试构建输出。
如果你为客户构建 Astro 网站并希望从一开始就内置安全加固,我们的 Astro 开发页面涵盖了我们如何设置项目的方法。
Nuxt:缓存投毒漏洞和 `runtimeConfig.public` 的暴露风险
CVE-2025-27415
发布于 2025 年 3 月 19 日。严重等级高,CVSS 评分 7.5,影响范围为可用性。漏洞条件:你的 Nuxt 应用运行在忽略查询字符串的 CDN 后面。攻击者请求类似 /?/_payload.json 的 URL。Nuxt 为该请求渲染 JSON。CDN 使用 / 作为缓存键缓存该 JSON,并将其提供给每个访问者。每次缓存过期时可重复发生。受影响版本:3.0.0 至 3.16.0 之前;3.16.0 版本已修复(安全公告)。
注意这类漏洞的特性。Nuxt 的代码和 CDN 的配置各自单独运行时表现良好。但结合在一起,它们破坏了你的首页。所以这个教训超越了"升级 Nuxt":CDN 缓存键配置是应用安全的一部分。你团队中应该有人负责它,且应该将其文档化,而不是依赖仪表板的默认设置。
进行快速的测试环境检查,仅使用你自己的测试域名:用奇怪的查询字符串请求页面,查看返回的内容,然后请求干净的 URL 并确认它仍然是 HTML。如果你的缓存层允许因性能原因忽略查询字符串,确保框架的内部路由被排除在该规则之外。
`runtimeConfig.public` 暴露的内容
Nuxt 将 runtimeConfig 分为私有键(仅服务器)和 runtimeConfig.public(发送到浏览器)。这个名称本身就是警告。任何在 public 下的内容都在页面负载中供任何人读取。把 Stripe 可发布密钥放在那里,没问题。把 CMS 管理员令牌放在那里,你就公开了它。
三个框架中的秘密和环境变量
每个框架都有一个前缀规则,该前缀意味着"发送到浏览器":
| 框架 | 设计上就是公开的 | 默认仅服务器端 |
|---|---|---|
| Next.js | NEXT_PUBLIC_*(在构建时内联) | process.env 中的所有其他内容 |
| Astro / Vite | PUBLIC_* | 其他环境变量,服务器端读取 |
| Nuxt | runtimeConfig.public | 顶级 runtimeConfig 键 |
阅读每个框架的文档了解这方面的细节,因为具体实现有所不同(Next.js 在构建时将 NEXT_PUBLIC_ 值内联到包中,这也意味着更改一个值需要重新构建)。原则没有区别。如果一个变量有公开前缀,就把它当作印在首页上。
三个习惯可以捕捉大部分错误:
- 永远不要把具有写入能力的令牌放在公开前缀后面,即使是"临时的"。
- 在部署前搜索你构建的输出(.next/static、dist/、
.output/public)中是否有令牌片段。 - 为你的前端提供一个只读的、范围狭窄的 CMS 凭证,与编辑使用的任何内容分开。
CVE-2025-55183 的教训
That Next.js source-exposure bug is a neat illustration. It could return compiled source of Server Functions. Secrets read from the environment at runtime weren't in that source. Secrets pasted into the code as literals were. The framework bug was the framework's fault; the leaked API key was ours. After patching, if a literal secret ever lived in a server function on an affected version, assume it was readable and rotate it. Moving it to an env var is the fix for next time, not for the past.
Jamstack 网站的安全头和 CSP
静态托管使得人们容易忘记设置头部,因为代码库中没有服务器配置文件。但头部是防止 XSS 后果(如上面的 Astro 服务器岛屿问题)的廉价且真实的保护手段。
基础设置应该应用于每个网站:Content-Security-Policy、Strict-Transport-Security、X-Content-Type-Options: nosniff、Referrer-Policy,以及 frame-ancestors(在 CSP 内)或 X-Frame-Options。
使用 Nonce 或哈希,而不是 `unsafe-inline`
允许脚本使用 'unsafe-inline' 的 CSP 对 XSS 防护几乎没有帮助。你有两个真正的选项:
- 哈希。构建时为每个内联脚本和样式生成 SHA-256 哈希。对于静态输出效果很好,因为内容不会按请求变化。这是 Astro 6 的
security.csp自动化处理的方式。 - Nonce。一个随机值,按请求生成,添加到头部和每个脚本标签中。需要服务器或边缘函数生成,因此更适合 SSR 而不是静态文件。Next.js 文档记录了一种使用其代理层的 nonce 模式。
根据渲染模型选择。纯静态:哈希。动态:nonce。
列出每个第三方源,按指令分类
这是乏味的部分,也是策略失败的地方。分析工具、标签管理器、字体、视频嵌入、聊天小部件、表单提供商、你的 CMS 图像主机。每个都需要列在正确的指令下:script-src、connect-src、img-src、font-src、frame-src。加载来自一个源的脚本并调用另一个源上的 API 的小部件需要同时列出两者。
一个示例头部,仅供参考(替换为你的实际源,nonce 必须按请求生成):
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-RANDOM' https://analytics.example.com; connect-src 'self' https://api.example.com; img-src 'self' data: https://cms.example.com; frame-ancestors 'none'; base-uri 'self'
从 Content-Security-Policy-Report-Only 开始,收集一两周的违规记录,修复列表,然后强制执行。并保持一份第三方源的简短清单和每个源被谁请求的记录。六个月后,没人记得为什么聊天小部件在 script-src 中。
npm 供应链:chalk、debug 和 Shai-Hulud
你的框架可能已被修补,服务器被锁定,但一个依赖仍然可以毁掉你的一周。两个 2025 年的事件说明了这一点。
chalk 和 debug,2025 年 9 月 8 日。一名维护者被一封来自 npmjs.help 的虚假 2FA 重置邮件钓鱼。攻击者发布了大约 18 个包的恶意版本,包括 chalk 和 debug。有效载荷目标是从浏览器包中窃取加密钱包活动。根据 Upwind 的分析,包在大约 2 到 2.5 小时内被移除。两小时很短。但在该时间窗口内解析新版本的 CI 任务会将有效载荷发送到生产环境。
Shai-Hulud,2025 年 9 月。CISA 于 2025 年 9 月 23 日的警告描述了一条自我复制的蠕虫,它破坏了 500 多个 npm 包。它收集了 GitHub 令牌和云密钥(AWS、GCP、Azure),将其发布到名为 Shai-Hulud 的公开 GitHub 仓库,并使用受害者自己的 npm 凭证将自己重新发布到其他包中。据报告,2025 年 11 月的第二波浪潮约影响了 796 个包(二手报道,所以将该数字视为报告数字)。
可以遏制它们的控制措施
两个事件都不需要巧妙的漏洞。两者都依赖于对"latest"的自动信任。因此:
- 提交你的锁文件并使用 `npm ci` 安装。它安装锁文件中明确指定的内容,如果
package.json不一致则失败。不会无声地漂移到被污染的版本。 - 添加冷却期。不要在版本发布后立即采用。pnpm 为此记录了最小发布年龄设置。即使只有一天的延迟也会完全避开 chalk/debug 时间窗口。
- 在可能的地方禁用安装脚本。像 Shai-Hulud 这样的蠕虫通过生命周期脚本运行。一个示例
.npmrc行:ignore-scripts=true。然后为确实需要构建步骤的少数包进行允许列表。 - 限制 CI 令牌的权限范围。构建任务很少需要发布权限或广泛的云密钥。短生命周期、最小权限的凭证意味着被盗的令牌价值有限。
- 遵循 CISA 的建议。审查锁定文件(包括嵌套依赖项),检查制品缓存,固定到已知安全版本,轮换开发者凭证,在 npm 和 GitHub 上要求抗钓鱼 MFA,启用秘密扫描、Dependabot 和分支保护。
同样的怀疑也适用于现在位于开发环境中的 AI 工具。我写了一篇关于如何审查 Claude Code 技能、插件和 MCP 服务器的文章,因为它们是第三方代码接近你的令牌的另一种方式。
Headless WordPress:解耦隐藏的是主题,而非 PHP
这是令团队惊讶的地方。使用 Headless 会移除公共主题层。但它不会移除 WordPress。PHP 应用程序、数据库、REST API 和 wp-admin 都仍然存在,通常在公共主机名上,因为前端需要访问它们。
wp2shell
2026 年的例子很直白。CVE-2026-63030(REST API 批量路由混淆,CVSS 9.8)与 CVE-2026-60137(WP_Query 的 author__not_in 中的 SQL 注入,CVSS 5.9)链接,在 WordPress 6.9.0 至 7.0.1 上造成未认证远程代码执行。默认安装,无需插件。入口点是 /wp-json/batch/v1。修复于 2026 年 7 月 17 日在 7.0.2、6.9.5 和 6.8.6 中发布,详见 Tenable 的常见问题。
攻击者行动迅速。Patchstack 在 7.0.2 发布后约 90 分钟内发现了第一次利用尝试,阻止了来自 1,500 多个 IP 的 65,000 多次尝试,发现 97% 的尝试针对批量端点。对于依赖防火墙的任何人,还有一个细节很重要:仅基于 URL 的 WAF 规则不够,因为 WordPress 在 POST 体中接受 rest_route。一条在路径中匹配 /wp-json/batch/v1 的规则会遗漏它。
锁定后端
目标:让 WordPress 源成为陌生人无法到达的东西,而不是仅仅寄希望于及时修补的东西。
- 尽可能将源保留在公网之外。使用 IP 允许列表、身份认证代理或前端与 CMS 之间的私有网络。如果你在构建时为静态网站提取内容,源只需要与构建运行器通信。
- 仅暴露前端使用的字段。裁剪 REST 响应和 GraphQL 模式。数据越少,可被滥用的就越少。
- 禁用未使用的路由,包括批量端点,如果堆栈中没有任何东西调用它。
- 限制 WPGraphQL。关闭公共内省和未认证查询,除非你确实需要它们。
- 限制应用密码和预览令牌的范围。每个集成一个,最小权限,集成结束时撤销。
- 保持核心自动更新开启。考虑到 90 分钟的利用窗口,手动每周修补的节奏不对。
如果你宁愿将这部分交给专家,我们的 WordPress 安全服务涵盖后端的锁定和监控。关于其周围的架构,请参阅 Headless WordPress 2026 指南和我们的 Headless WordPress 开发页面。
托管和边缘控制
平台在保护方面有所不同,值得了解具体情况而不是假设。以 CVE-2025-29927 为例:Vercel 托管的应用程序在基础设施级别获得保护,而自托管的则没有。这是托管平台的真实、有案例的优势。Netlify 和 Cloudflare 同样在其边缘运行保护层(WAF 功能、DDoS 缓解、托管规则,取决于计划)。自托管让你获得控制权,但要把这些层加入你的待办事项。
诚实的框架:边缘保护为你争取时间。它们不能替代修补。今天阻止一种利用模式的平台规则对下一个安全公告没有任何说明,而运行良好的修补流程的自托管设置可能完全可靠。
无论在哪里托管,都要配置什么:
- WAF 规则要检查请求体,而不仅仅是路径。wp2shell
rest_route细节是要记住的例子。 - 对表单处理程序、服务器操作、登录路由以及任何触发高成本工作的端点设置速率限制。这也能防止 CVE-2025-55184 这样的拒绝服务漏洞。
- 图像 `remotePatterns`。在 Next.js 中,images.remotePatterns 限制图像优化器将从哪些远程主机获取。Astro 有等效的图像域和模式设置。只允许你的 CMS 主机,其他都不允许。开放的图像代理是 SSRF 的邀请。
- 缓存键审查。根据 Nuxt 事件,了解你的 CDN 忽略什么。
- 源站保护。让应用源站仅接受来自你的 CDN 或设置允许的平台的流量,这样攻击者就无法绕过边缘层。
如果你在 Next.js 上构建,后面有数据库,Next.js 和 Supabase 开发页面展示了我们如何限制服务端访问范围。
加固检查清单
根据你的技术栈进行调整。下面列出的每个事件都来自前面引用的来源;请在每个链接的安全公告中确认当前的修复版本,而不是信任任何表格(包括这个)中的版本号。
| 层 | 风险 | 2025-26 真实例子 | 控制 | 如何检查 |
|---|---|---|---|---|
| Next.js | RSC 远程代码执行;拒绝服务;源代码泄露;仅中间件认证 | CVE-2025-55182 / CVE-2025-66478(2025 年 12 月 3 日);CVE-2025-55184、CVE-2025-55183(2025 年 12 月 11 日);CVE-2025-29927(2025 年 3 月 21 日) | 升级到你的版本线的修补版本;在数据层、操作和路由处理程序中进行授权;服务器函数中不要硬编码密钥;限制 images.remotePatterns | 根据安全公告运行 npm ls next react;在代码中搜索密钥字面量;禁用 proxy.ts 后测试受保护的数据是否仍然受保护 |
| Astro | 服务器岛屿 XSS;图像端点 SSRF;中间件路径绕过 | CVE-2025-64764(2025 年 11 月 19 日);CVE-2025-58179;CVE-2025-64765 | 升级 Astro 和 @astrojs/cloudflare;在 Astro 6 上启用 security.csp;允许列出图像来源;不要依赖 URL 匹配进行身份验证 | 根据安全公告运行 npm ls astro @astrojs/cloudflare;在构建或预览版本上检查响应头,而不是开发版本 |
| Nuxt | CDN 载荷缓存中毒;公共配置中的密钥 | CVE-2025-27415(2025 年 3 月 19 日) | 运行 3.16.0 或更高版本;在缓存键中包含查询字符串或从缓存中排除内部路由;runtimeConfig.public 中仅包含非密钥值 | 在暂存环境中,请求 /?/_payload.json,然后请求干净的 URL,并确认 HTML;在构建的输出中搜索令牌片段 |
| Headless WordPress后端 | 通过 REST 批处理路由的未身份验证 RCE;公开的管理员和 API 表面 | wp2shell:CVE-2026-63030 + CVE-2026-60137(已于 2026 年 7 月 17 日修复) | 已打补丁的核心,启用自动更新;源站位于允许列表或代理后;禁用未使用的路由;WPGraphQL 内省受限;作用域应用密码 | 在仪表板中检查核心版本;从允许列表外部请求 /wp-json/batch/v1;确认 WAF 检查 POST 请求体 |
| 构建管道 | 受污染的依赖;被盗的 CI 和开发者令牌 | chalk/debug(2025 年 9 月 8 日);Shai-Hulud(CISA,2025 年 9 月 23 日) | 已提交的 lockfile 和 npm ci;关闭安装脚本;发布冷却期;作用域、短期有效的 CI 令牌;抗网络钓鱼 MFA;密钥扫描 | 在拉取请求中审查 lockfile 差异;审计 CI 令牌作用域;确认启用了 Dependabot、密钥扫描和分支保护 |
FAQ
Pages Router 是否受 2025 年 12 月 Next.js 问题的影响?
对于 CVE-2025-55183(源代码泄露)和 CVE-2025-55184(拒绝服务),Next.js 公告将受影响的路径描述为具有服务器函数的 App Router,并表示 Pages Router 不受源代码泄露影响。不要以此理解为"永久安全"。React2Shell 也针对 App Router 中的 React Server Components,你仍然需要使用打过补丁的版本。查看 2025 年 12 月的公告以了解你的具体版本行。
打补丁后应该轮换密钥吗?
如果密钥曾被硬编码在受影响 Next.js 版本的服务器函数中,那么是的。假设它可能已被读取,因为该漏洞返回了编译后的源代码。轮换它,然后将替换项移至运行时环境变量。仅从环境读取的密钥不在该源代码中,因此对它们的泄露论证要弱得多。
如何在不阅读每个更改日志的情况下掌握公告?
关注你依赖的存储库的 GitHub 安全公告(Next.js、withastro/astro、Nuxt),并为你自己的存储库启用 Dependabot 警报。将负责打补丁的人员订阅这些源,而不是一个无人阅读的共享收件箱。对于 WordPress 核心,保持自动更新开启并添加漏洞源,例如 Patchstack 的。
如果前端仅在构建时提取内容,我可以保持 wp-admin 公开吗?
可以,但不应该。如果前端仅在构建期间需要源站,就没有理由向所有人公开它。将你的构建运行器和编辑人员的 IP 加入允许列表,或在其前面放置身份验证代理。使用变更 IP 的编辑是常见抱怨,具有单点登录的代理可以解决此问题。
打补丁速度和缓存键配置是团队最常出错的两件事:Next.js 和 WordPress 漏洞在披露后数天或数小时内到达,而 Nuxt 漏洞仅因 CDN 忽略查询字符串而存在。检查在请求时实际运行的内容,在数据层而不是代理中进行授权,并将 WordPress 源站保持离开开放互联网。
