技术深度剖析:React Server Components 的工作原理及漏洞出现位置
Source: Dev.to
一个简化但准确的 RSC 请求生命周期如下:
-
传入请求到达 RSC 端点
- 在 Next.js 中,这通常映射到内部路由,例如
/_rsc。 - 请求在身份验证和应用中间件之前被处理。
- 在 Next.js 中,这通常映射到内部路由,例如
-
请求体解析和协议解码
- 按照 React Flight 协议解析负载。
- 这包括解码序列化的组件引用、props 和执行提示。
- 在此阶段,React 假设负载的结构足够有效以继续处理。
-
组件图重建
- React 从序列化的输入中重建服务器组件树。
- 模块引用被解析并加载。
- 异步边界和 Suspense 段被注册。
-
服务器渲染与流式传输
- 组件在服务器上执行。
- 输出再次序列化并以块的形式流回客户端。
- 错误边界和部分结果会增量刷新。
-
错误与恢复
- 如果在流式传输过程中出现异常,React 会尝试优雅恢复。
- 元数据、堆栈跟踪和模块标识符仍然在内部可用。
- 这些阶段都对格式错误或恶意输入高度敏感。
拒绝服务(DoS)是如何触发的
DoS 问题主要出现在第 2 步和第 3 步,在任何应用逻辑介入之前。
恶意请求可以:
- 膨胀序列化负载的大小。
- 引入深度嵌套或循环引用。
- 强制过度的异步边界解析。
概念示例(JSON 负载):
POST /_rsc
Content-Type: application/json
{
"type": "ServerComponent",
"props": {
"children": {
"children": {
"children": {
"...": "..."
}
}
}
}
}
即使负载在语法上是有效的,React 也会:
- 尝试反序列化它。
- 构建内存中的组件图。
- 调度执行和流式工作。
这会导致:
- 解码期间 CPU 使用率飙升。
- 图重建期间内存压力增大。
- 工作线程或事件循环饱和。
关键是,这一切发生在不触及用户自定义代码且不经过身份验证的情况下,使得传统的应用层防御假设基本失效。
源代码泄露是如何产生的
源代码泄露是 React 在第 4 步和第 5 步处理错误时的副作用。
当渲染或序列化错误在流式传输中途发生时:
- React 仍然持有内部模块路径的引用。
- 堆栈跟踪中包含已解析的文件位置。
- 组件元数据会部分序列化。
在某些边缘情况,这些信息可能:
- 泄漏到流式响应中。
- 出现在错误负载里。
- 在流被正确中止前被刷新出来。
泄漏信息示例(JavaScript 错误):
Error: Failed to resolve Server Component
at /app/src/components/admin/UserList.server.tsx
at react-server-dom-webpack/server
这会暴露:
- 项目目录结构。
- 仅服务器可用的组件边界。
- 内部模块布局和命名。
虽然这本身不是独立的利用手段,但会显著降低针对性攻击的准备成本。
为什么不需要 Server Actions
一个常见的误解是这些风险仅在使用 Server Actions 时才会出现。实际上:
- Server Actions 只是一个 API 表面。
- RSC 是运行时和协议。
即使是一个简单的页面:
export default async function Page() {
return ;
}
仍然会触发:
- 请求反序列化。
- 组件图重建。
- 服务器执行与流式传输。
易受攻击的逻辑会在不定义 Server Actions 的情况下执行。
关键要点
- React Server Components 引入了一种根本性的服务器端执行模型,它将请求反序列化、组件图重建以及基于流的渲染合并为单一的框架管理流水线。
- 有效的攻击面存在于 身份验证之前、应用中间件之前、以及 任何用户自定义业务逻辑执行之前。
- 格式错误或对抗性的 RSC 请求可能导致:
- 拒绝服务:在反序列化和组件图重建期间导致 CPU 过度使用、内存压力以及工作线程或事件循环饱和。
- 意外泄露服务器实现细节:包括内部文件路径、组件边界和模块布局,作为流式错误处理的副作用。
- 这些问题并非由配置错误的应用或不安全的用户代码引起;它们是 React Server Components 运行时本身的框架级漏洞,单靠应用层防御难以可靠缓解。