React RCE 事件留下的教训:为什么现在需要 HMAC 签名、密钥轮换和零信任

RCE 漏洞展示的真相:"一旦信任数据,终将被突破"



最近在 React Server Components/Next.js 中出现的 RCE 漏洞(CVE-2025-55182)并不只是“React 被突破”的新闻。它所传递的核心信息更具原则性。

“只要一次信任客户端发送的数据,终将被突破。”

此次漏洞的根本原因是服务器在没有充分验证的情况下,直接使用了客户端发送的 Flight 协议数据(元数据) 进行模块加载和对象访问。

  • “这个值由 React 自动安全处理。”
  • “我们没有直接使用的 API,应该没问题。”

这种隐式信任的累积,最终导致了 在未进行预认证的情况下就能实现 RCE 的点。

现在我们需要改变提问方式。

  • “React 为什么被突破?”
  • “我们现在在没有任何签名或验证的情况下,信任哪些数据?”

在寻找答案的过程中,自然而然会出现 HMAC 签名零信任 这两个概念。


为什么 HMAC 签名很重要:"这数据真的来自我们的服务器吗?"

现实中的系统往往过度依赖“服务器之间的信任”。

  • 前端 ↔ 后端
  • 微服务 A ↔ B
  • 后端 ↔ 后端(异步任务、队列、Webhook、内部 API 等)

在这些交互中,数据往往被假设为安全的。

“这个 URL 没有暴露给外部,只有内部系统会调用。”

“这个 token 格式只有我们服务知道,安全。”

但攻击者总是从打破这些“假设”开始。

此时,HMAC(如 HMAC-SHA256) 的存在是为了回答一个根本性的问题。

这请求/消息,是否真的由“我们拥有的秘密密钥”的人创建?

更详细地说:

  • 只有知道 HMAC 密钥的一方才能生成 有效签名(signature)
  • 服务器接收 payload + signature,即可验证
  • “签名是否正确”,
  • “中间是否被篡改”。

没有 HMAC 签名的世界

没有 HMAC 签名的 REST/Webhook/内部 API 会怎样?

  • 假设攻击者已经了解了 URL 结构和参数,
  • 可以随意构造请求并发送,服务器会将其误认为“自然的内部请求”。

例如:

POST /internal/run-action
Content-Type: application/json

{
  "action": "promoteUser",
  "userId": 123
}

即使原本是“后台只允许内部调用”的端点,

  • 网络边界被突破,
  • 内部代理存在漏洞,
  • CI/CD、日志、示例代码、文档被外部访问,

攻击者就能伪装成内部系统地调用该端点。

此时,如果存在 HMAC 签名,情况会完全不同。

  • 在请求体上附加 HMAC 签名,
  • 服务器每次请求都验证 signature

“即使知道 URL 或参数,没有密钥也无法生成有效请求。”


示例:用 HMAC 签名保护内部操作



下面给出一个简易示例(TypeScript/Node.js 伪代码)。

1. 服务器拥有共享秘密密钥

const HMAC_SECRET = process.env.HMAC_SECRET!; // 从 .env 或 Secret Manager 注入

2. 客户端(或内部服务)构造请求时

import crypto from 'crypto';

function signPayload(payload: object): string {
  const json = JSON.stringify(payload);
  return crypto
    .createHmac('sha256', HMAC_SECRET)
    .update(json)
    .digest('hex');
}

const payload = {
  action: 'promoteUser',
  userId: 123,
};

const signature = signPayload(payload);

// 发送
fetch('/internal/run-action', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Signature': signature,
  },
  body: JSON.stringify(payload),
});

3. 服务器验证时

function verifySignature(payload: any, signature: string): boolean {
  const json = JSON.stringify(payload);
  const expected = crypto
    .createHmac('sha256', HMAC_SECRET)
    .update(json)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature, 'hex'),
    Buffer.from(expected, 'hex'),
  );
}

app.post('/internal/run-action', (req, res) => {
  const signature = req.headers['x-signature'];

  if (!signature || !verifySignature(req.body, String(signature))) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // 到此为止意味着:
  // 1) payload 未被篡改,
  // 2) 由知晓 HMAC_SECRET 的主体创建请求
  handleInternalAction(req.body);
});

此结构即是“这数据真的来自我们吗”的最基本证明模式。


多名运维/开发人员时:HMAC 密钥轮换是“非选择而是必需”

HMAC 密钥的另一个现实问题是

  • 运维、SRE、后端开发者、外包人员、实习生…
  • 并非所有人都能访问 .env
  • 但 GitOps、CI/CD、文档、Slack、Notion 等任何地方都有可能泄露密钥。

关键在于:

“密钥可能泄露”这一假设必须被视为前提。

也就是说,“一定会泄露”,并据此进行设计。

因此,HMAC 密钥轮换(rotation) 成为必需。

为什么需要轮换

  1. 人员变动 * 离职、团队变更、外包结束等。 * 过去能看到密钥的人现在不再能看到。
  2. 无法完全追踪泄露 * 过去的 Slack DM、本地笔记、截图、备忘… * “从未泄露”几乎不可能保证。
  3. 事故后仍需可恢复 * 若某时点之前的密钥泄露, * 必须能声明“从此以后只接受新密钥签名的请求”。

实务中的 HMAC 密钥轮换模式

在多名运维/管理员的组织中,通常推荐以下模式。

  1. 密钥版本管理 * 例如 HMAC_SECRET_V1HMAC_SECRET_V2, * 在请求头或负载中携带 kid(key id)。
  2. 服务器同时持有多把密钥,验证时按版本处理 * 例如:先验证 V1、V2, * 一段时间后废弃 V1,只允许 V2。
  3. 轮换流程文档化 * “生成新密钥 → 部署 → 两边(生成者/验证者)切换到新密钥 → 删除旧密钥”, * 用 checklist + runbook 记录,即使人员变动也能按流程执行
  4. 权限分离 * 负责生成/管理密钥的人与修改应用代码的人分离, * 若可能,进一步考虑。

当 HMAC 密钥成为“一次性决定的值”时, 密钥泄露即导致整个系统崩溃

密钥轮换是将此风险随时间限制的技术手段。


零信任:"信任不是结构,而是每一次请求的生成"

最后,React RCE 事件再次提醒我们安全的基本原则。

安全的根本是零信任(Zero Trust)。

零信任的核心非常简单。

  • “因为内部所以没问题”
  • “前端只用的值所以安全”
  • “我们自己的框架所以一定被阻止”

这些说法本质上被禁止

相反,应该问:

  • “在假设输入可能是恶意的情况下,是否已设计?”
  • “这请求是否真的来自我们预期的主体?”
  • “如何确认数据未被篡改?”
  • “如果密钥泄露,影响范围如何,如何恢复?”

React RCE 事件也可以从这个框架来看。

  • RSC Flight 数据被视为“React 提供的”,
  • 结果导致客户端可操纵的值直接影响服务器模块加载路径

从零信任角度看,这本应一开始就被视为危险信号

“客户端可操纵的序列化数据影响服务器代码执行路径 = 必然的风险信号”

在此类场景中,首要的防御措施是:

  • HMAC 签名(完整性与来源验证)
  • 密钥轮换(在密钥泄露假设下限制影响范围)

总结:"不要信任,必须验证"

本文想传达的核心信息只有三点。

  1. 不要默认信任数据 * 即使是“框架内部使用的数据”, * 只要跨网络传输,随时可能成为攻击面
  2. HMAC 签名是验证“数据确实来自我们”的基本工具 * REST API、Webhook、内部 RPC、异步消息等, * 所有服务器间通信都应考虑 HMAC。 * 多名管理员时,必须将密钥轮换纳入流程
  3. 安全的根本是零信任 * “内部就安全”是危险信号。 * 信任必须通过每一次请求、每一份数据的验证来构建。

React RCE 事件提醒我们,在前后端边界模糊的时代,我们需要保持警惕

“不要信任,验证。将验证写进代码、流程、密钥管理政策。”

第一步是 HMAC 签名与密钥轮换,构建在其上的哲学即是 零信任

image