Forgejo 16.0.5 - 高危 - 远程 - SSRF - hostmatcher 允许将 0.0.0.1 视为公网 IP
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · forgejo-hostmatcher-0001-ssrf
类别: SSRF 可达性: 远程
Forgejo 16.0.5(8d24b7d)- Forgejo
默认的 [webhook] ALLOWED_HOST_LIST 为空,因此 services/webhook/deliver.go 使用内置的 external 构建客户端。该谓词是 Go 的 IsGlobalUnicast() && !isPrivate()。isPrivate 是 RFC1918/4193 加上 v16.0.2 的 CGNAT 补充。RFC 6890 的 0.0.0.0/8 不属于其中任何一项。127.0.0.1 是回环地址,被拒绝。0.0.0.1 被允许。
已登录用户可以将 webhook 指向 http://0.0.0.1,并从 hook 历史记录中读取 HTTP 响应体。同一端口上的 127.0.0.1 会被拒绝。
| ID | 暂无 CVE |
| 类别 | SSRF |
| 可达性 | 远程(已认证;任何能创建仓库 webhook 的用户) |
| CWE | CWE-918 |
| CVSS | 高危:7.7 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N |
| 产品 | Forgejo |
| 受影响版本 | 直至 v16.0.5(8d24b7d);v16.0.2 CGNAT 补充的遗留问题 |
| 认证 | 能创建仓库 webhook 的已登录用户 |
| 许可证 | GNU Affero GPL v3.0 |
| 实验环境 | 仅 127.0.0.1 |
在默认安装上注册,创建一个仓库,将 forgejo webhook 设置为 http://0.0.0.1:<port>/...,测试它,然后读取响应体。
| 权限 | 方法 / 汇聚点 | 侦察 / 完整性 / 可用性 | 实验验证或推断 |
|---|---|---|---|
拨号本网络 0.0.0.1 绕过默认 webhook 允许列表 | POST /api/v1/repos/{owner}/{repo}/hooks 然后 POST .../hooks/{id}/tests,URL 为 http://0.0.0.1:8080/ssrf | 侦察(从 Forgejo 主机发起出站 HTTP) | 实验验证。历史记录存储了 HTTP 200 响应体 FORGEJO-HOSTMATCHER-0001-WITNESS |
| 读取任何应答者的完整 HTTP 响应 | GET /{owner}/{repo}/settings/hooks/{id} 打印 ResponseInfo.Body | 侦察(全响应 SSRF) | 实验验证。历史页面中有相同的见证字符串 |
通过 IPv4 映射的 [::ffff:0.0.0.1] 实现同类攻击 | 相同的匹配器 To4() 然后 checkIP | 侦察 | 从 hostmatcher 推断。实验使用 0.0.0.1 |
| 访问在 Forgejo netns 中绑定范围比回环更广的监听器 | 一旦 0.0.0.1 位于 lo 上,Linux connect() 到 0.0.0.0/8 即为本主机 | 侦察 | 针对 sidecar oracle 实验验证。并非关于特定云 IMDS 的声明 |
在相同的默认匹配器上被关闭:127.0.0.1(实验验证在历史记录中被拒绝)、127.0.0.0/8、RFC1918、CGNAT 100.64.0.0/10、链路本地 169.254.0.0/16(包括 IPv4 IMDS)、未指定地址 0.0.0.0。出站 webhook 不会获得 X-Forgejo-Internal-Auth / INTERNAL_TOKEN。这本身不是 RCE。
这不是 v16.0.2 的 CGNAT 修复。Forgejo 从未增加 Gitea 风格的保留网段列表。与 gitea-hostmatcher-0000-ssrf 属于同一类别。
v16.0.2 将 CGNAT 放入了 private。添加拒绝列表的补丁是一份礼物:你阅读列表,然后问 IANA 还有什么被列表遗忘了。一次同类狩猎声称 Go 1.26 的 IsGlobalUnicast 关闭了 0.0.0.1。我检查了谓词和树内测试。它们从未覆盖 0.0.0.1。
已记录的弯路:将 webhook 创建返回 HTTP 201 当作 oracle(历史记录才是 oracle);127.0.0.1 投递成功(那会是另一个 bug——实验要求它被拒绝);使用 X-Forgejo-Internal-Auth 访问 /api/internal;反弹 shell。都是花架子。
python3 forgejo-hostmatcher-0001-ssrf-Abraxas-Labs.py
或:
cd lab
./run.sh
目标仅限 http://127.0.0.1:19320。镜像 codeberg.org/forgejo/forgejo:16.0.5。Oracle sidecar 在端口 8080 上共享 Forgejo netns。
create-hook name=neg-loopback ...
negative-control 127.0.0.1 blocked
create-hook name=hit-0001 ...
poll id=2 witness=True
SUCCESS forgejo-hostmatcher-0001-ssrf how=live pin=v16.0.5 0.0.0.1:8080 HTTP 200 FORGEJO-HOSTMATCHER-0001-WITNESS
将 RFC 6890 的 0.0.0.0/8 视为非外部地址(使用保留网段列表,而非 IsGlobalUnicast)。0.0.0.1 必须像 127.0.0.1 一样被拒绝。
hostmatcher.goGNU Affero GPL v3.0。参见 LICENSE。仅限回环实验环境。无担保。