安全默认演示实验室,展示容器加固(无发行版镜像、非root、只读文件系统、运行时注入的机密)如何中和关键的Next.js/React Server Actions RCE(CVE-2025-55182“React2Shell”),并并排显示安全与不安全部署及漏洞利用日志
本项目演示了 Next.js 应用程序(特别是通过服务器操作)中的一个严重远程代码执行 (RCE) 漏洞,以及基础设施加固如何在代码漏洞仍然存在的情况下有效阻止攻击。
它对比了使用Distroless镜像和只读文件系统的标准“不安全”部署与强化后的“安全”部署。
软件漏洞不可避免。当代码失效时,你的基础设施必须阻止攻击者扩大立足点。
Next.js 使用的 React 服务器组件 (RSC) 实现中存在一个严重 RCE 漏洞(CVE-2025-55182,又名 React2Shell)。
spawnSync)。curl、wget、ls、cat)窃取机密或下载恶意软件。
child_process.spawnSync()。它直接执行二进制文件,无需 shell(/bin/sh)。chmod +x),然后运行。以下日志展示了从应用程序视角看攻击尝试的样子。这种对比生动地凸显了安全措施的有效性。
logs/server.safe.log)日志显示重复的失败(ENOENT)。
spawnSync 尝试运行 ls、id、curl。Distroless 镜像根本没有这些二进制文件。这不仅是没有 shell,工具本身也不存在。[Instrumentation] Logging initialized. Writing to: /app/logs/server.safe.log
⨯ Error: NEXT_REDIRECT
... digest: '`{"step":"1. Write Initial Chunk to /tmp/hello_test","success":true}`'
⨯ Error: NEXT_REDIRECT
... digest: '`{"step":"2. Verify Binary was Written","verification":{...},"success":true}`'
⨯ Error: NEXT_REDIRECT
... digest: '`{"step":"4. Execute Binary","stdout":"","stderr":"","error_obj":{"message":"spawnSync /tmp/hello_test EACCES","code":"EACCES"},"success":true}`'
logs/server.unsafe.log)日志确认命令执行成功和文件系统操作。
[Instrumentation] Logging initialized. Writing to: /app/logs/server.unsafe.log
⨯ Error: NEXT_REDIRECT
... digest: '`{"step":"1. Write Initial Chunk to /tmp/hello_test","success":true}`'
⨯ Error: NEXT_REDIRECT
... digest: '`{"step":"4. Execute Binary","stdout":"Hello from Go binary!\\n","stderr":"","error_obj":null,"success":true}`'
⨯ Error: NEXT_REDIRECT
... digest: '`{"command":"id","args":[],"stdout":"uid=0(root) gid=0(root) ...","stderr":"","status":0,"signal":null}`'
⨯ Error: NEXT_REDIRECT
... digest: '`{"command":"cat","args":["/app/.env"],"stdout":"","stderr":"cat: can\'t open \'/app/.env\': No such file or directory\n","status":1,"signal":null}`'
(注意:在不安全日志中,上面的 cat /app/.env 失败是因为文件在根目录下名为 .env,但在完整日志中 ls -la 会显示目录结构。)
尝试运行标准 shell 命令。
id、ls、cat .env,并访问敏感数据。spawnSync /bin/sh ENOENT。没有 shell 可以执行命令。尝试通过上传自定义二进制文件绕过“缺少工具”。
/tmp/malware。chmod +x。EROFS: read-only file system。攻击者能否将二进制文件加载到变量中并直接从内存执行?
global.payload = "..."),然后执行它。child_process 函数(spawn、exec)需要文件路径。它们无法直接执行缓冲区或字符串。memfd_create(一个在 RAM 中创建匿名文件的系统调用)。memfd_create。要使用它,需要预先在 node_modules 中安装 C++ 插件(如 ffi-napi)。gcc、make),攻击者无法即时构建此插件。“Distroless”镜像只包含你的应用程序及其运行时依赖。它们不包含包管理器、shell 或标准 UNIX 工具。
ls)、下载文件(curl)或轻松提升权限。配置你的容器运行时,将根文件系统挂载为只读。
docker-compose.yml 中:
read_only: true
tmpfs:
- /tmp:noexec # 关键:明确阻止执行!
/tmp(写入成功),但由于 noexec 标志,执行失败并返回 EACCES(权限被拒绝)。这平衡了功能(可写 tmp)和安全性。不要在你的容器镜像中附带 .env 文件。如果攻击者可以读取文件(例如 cat .env),你的机密就会泄露。
environment 键)。启动环境:
安全和不安全的应用程序都定义在单个 docker-compose.yml 文件中。
docker compose up --build -d
运行漏洞利用: 你可以针对特定端口运行漏洞利用,以查看差异。
针对不安全应用(端口 3001):
# 1. 标准 RCE(LotL) - 成功
python exploit/poc.py http://localhost:3001
# 2. 高级攻击(BYOL) - 成功
python exploit/poc_advanced.py http://localhost:3001
针对安全应用(端口 3000):
# 1. 标准 RCE(LotL) - 失败(ENOENT)
python exploit/poc.py http://localhost:3000
# 2. 高级攻击(BYOL) - 失败(EACCES/EROFS)
python exploit/poc_advanced.py http://localhost:3000
清理:
docker compose down
| 特性 | ❌ 不安全环境(端口 3001) | ✅ 安全环境(端口 3000) |
|---|
| 基础镜像 | node:20-alpine(包含 ls、curl、wget 等) | gcr.io/distroless/nodejs20-debian12(无 shell,无工具) |
| 文件系统 | 可写(标准 Docker 默认) | 只读(read_only: true) |
| 机密 | 磁盘上的 .env 文件(易受 cat .env 攻击) | 环境变量(运行时注入) |
| 用户 | root(默认) | 非 root(Distroless 强制) |