Arturo0x90/CVE-2026-51385
GitHub: Arturo0x90/CVE-2026-51385
针对 graphify 知识图谱工具中 DNS rebinding 绕过 anti-SSRF 防护的漏洞公开安全通告(CVE-2026-51385)。
Stars: 0 | Forks: 0
# CVE-2026-51385
关于 CVE-2026-51385 的安全通告。由于 GRAPHIFY 既未承认也未发布该通告,而 MITRE 已分配了 CVE-2026-51385,因此有必要将其发布,以下是其安全通告。
ES:
Este fue mi primer cve, para ser honesto encontre una manera de romper la logica en la funcion ANTI SSRF, y realmente queria publicar mi primer CVE
asi que pense como podria afectar a la seguridad para demotrar impacto, aunque fuera compleja de ejecutar (es complicado que esto ocurra en una instancia real, pero posible por eso tiene complexity high). Lo envie al mitre y lo aceptaron. Este es el advisory.
EN:
This was my first CVE, to be honest i found myself the way to brake the logic in the anti-SSRF function, and i really wanted to publish the CVE
so i tough how it could affect the security, and found a justification, send it to MITRE and they accept it. This is the advisory.
**Keep in mind the report was partially made with AI, but supervised by a human**
# CVE-2026-51385
graphify 中通过 DNS rebinding (TOCTOU) 引发的 SSRF。
**Package:** `graphify` (PyPI: `graphifyy`),代码仓库 [`Graphify-Labs/graphify`](https://github.com/Graphify-Labs/graphify)
**受影响组件:** URL 获取路径,`graphify add `
**受影响版本:** `>=0.3.2, <=0.4.29`
**修复版本:** `0.5.4`(提交记录 [`dd86271`](https://github.com/Graphify-Labs/graphify/commit/dd86271),PR #591 / #592)
**CVSS 3.1:** 8.3 (High),`CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H`
**CWE:** [CWE-918](https://cwe.mitre.org/data/definitions/918.html) (SSRF),[CWE-367](https://cwe.mitre.org/data/definitions/367.html) (TOCTOU 竞争)
**报告者:** Arturo Melgarejo Galindo,独立安全研究员 ([@Arturo0x90](https://github.com/Arturo0x90))
## 漏洞背景
`graphify add ` 尝试像许多抓取器那样保护自身免受 SSRF 攻击:它解析主机名,根据黑名单(loopback、RFC1918、link-local、reserved)检查解析出的 IP,只有在检查通过时才进行抓取。
问题在于,它验证的 IP 并不是它实际连接的 IP。验证阶段进行了一次 DNS 查询,随后 `requests.get(hostname)` 又进行了第二次独立的 DNS 查询。如果你拥有该主机名的 DNS 区域控制权,并设置极低的 TTL 和两条 A 记录(一条公网,一条内网),解析器完全可以在每次查询时合法地给出不同的答案。第一次查询的答案通过了黑名单检查。第二次查询的答案才是 socket 实际连接的目标。这就是该漏洞的全部原理。这是针对“先检查 IP,再按主机名连接”逻辑的经典 rebinding 绕过,而修复方法是将验证过的 IP 固定到连接中,这正是 `0.5.4` 版本所做的。
## 实际产生危害的场景
我想对此保持坦诚,因为单看表面,它的危害似乎比实际要小。
如果一个人亲自坐下来,将自己信任的 URL 输入到 `graphify add` 中,这几乎毫无价值。如果那个人愿意,他们直接把 graphify 指向自己的 `127.0.0.1` 就行了。他们根本不需要 rebinding 这种把戏,而且在这个场景中也不存在攻击者。
当 graphify 去获取操作者无法决定的 URL 时,它就变成了一个真正的漏洞。对于这个工具来说,这不是什么边缘情况,而是它的常规使用方式。graphify 构建的知识图谱会被 AI 助手消费,因此真实的流程是某个进程代表你调用 `graphify add`:可能是一个 agent、一个 CI 任务、一个遍历 README 中 URL 列表的脚本,或者是直接从另一个模型输出中提取的 URL。在所有这些情况下,输入字符串都是由攻击者控制的,而人类根本没有检查过它。
这才是真正要紧的情况。一旦输入不受信任并且赢得了 rebinding 竞争,抓取请求就会落在内部地址上,而不是它之前验证过的公网地址。具体来说,它可以访问到:
* `127.0.0.1` 以及任何绑定到 loopback 的地址,
* `169.254.169.254`(云实例元数据),
* 从受害者机器可达的 RFC1918 主机,
* 以及黑名单完全没有覆盖的 CGN 地址段 `100.64.0.0/10`——这个甚至连竞争都不需要,一条普通的指向该网段的 A 记录就足够了。
而能访问到这些地址之所以会造成破坏,完全是因为这些地址上运行的服务。许多内部和开发服务暴露了 GET endpoint,它们要么直接返回数据,要么仅通过一个单纯的 GET 请求就会改变状态。因此,当 graphify 的抓取请求带着错误的路径和查询字符串落在这些服务上时,它就不只是一个读取操作了。如果内部目标是 Jenkins 的 `scriptText`、运行在 `--debug` 模式下的 Flask/Django 调试器、管理面板,或者是元数据 endpoint,那么那个畸形的 GET 请求就等同于攻击者从网络内部发起的攻击。graphify 就成了替他们发送请求的代理人。
我并不是说这会导致 RCE。它本身并不会。但是,一个能够通过自动化获取路径指向 loopback、IMDS、RFC1918 和 CGN 的完全 SSRF,正是构建那些攻击的基础原语,而这正是报告此漏洞的意义所在。
## 概念验证
我使用了 Tavis Ormandy 公开的 `rbndr.us` 测试工具,它提供了一个在每次解析时会在两个 IP 之间翻转的主机名。`7f000001.08080808.rbndr.us` 会在 `8.8.8.8` 和 `127.0.0.1` 之间交替。
在内部目标上启动一些东西(这里为了演示,使用 loopback):
```
sudo python3 -m http.server 80
```
然后触发获取并不断重试,直到竞争成功。它大约每 4 到 5 次尝试就能命中 1 次,一个简单的循环就能将成功率轻松推高至 99% 以上:
```
for i in {1..20}; do
graphify add http://7f000001.08080808.rbndr.us/ && break
sleep 1
done
```
在尝试成功时,即使主机名首先通过了 IP 黑名单检查,graphify 依然会抓取并获取本地服务器 `127.0.0.1:80` 返回的内容。
## 时间线
* 2026-04-20:私下报告给维护者。
* 2026-04-28:8 天后在 `0.5.4` (`dd86271`) 中提交了修复程序。
* 2026-07-18:发布公开安全通告,CVE 已提交给 MITRE 进行发布。
## 致谢
Arturo Melgarejo Galindo,独立安全研究员。
标签:CISA项目, DNS重绑定, Python, SSRF, 安全公告, 无后门, 漏洞披露, 逆向工具