système : OPÉRATIONNEL
← retour à tous les hacks
INFRASTRUCTURE CRITICAL NEW

RAGFlow CVE-2026-45312 : un modèle de prompt qui exécute des commandes système

Une injection de template Jinja2 dans le générateur de prompts de RAGFlow transforme un champ contrôlé par l'utilisateur en RCE côté serveur. CVSS 9.9, divulguée le 9 mai 2026.

2026-06-20 // 7 min affects: ragflow

De quoi s’agit-il ?

CVE-2026-45312 est une faille d’exécution de code à distance dans RAGFlow, l’un des moteurs de RAG (génération augmentée par récupération) open source les plus déployés. Elle a été signalée par Yuu (VNUHCM-UIT) de Verichains et publiée dans l’avis fournisseur GHSA-wpg4-h5g2-jxm6 le 9 mai 2026 ; l’enregistrement CVE est arrivé au NVD le 29 mai 2026. Elle affecte les versions de RAGFlow jusqu’à 0.24.0 incluse, porte un CVSS de 9.9 (AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H) et est classée CWE-1336, injection de template côté serveur.

Le bug est un cas d’école d’un problème que cette catégorie de produits ne cesse de répéter : un modèle de prompt est rendu comme du code. Un champ qui ressemble à de la configuration est passé directement à un moteur de templates, et ce moteur est Turing-complet.

Comment ça marche

RAGFlow construit certains de ses prompts avec Jinja2. Le générateur de prompts dans rag/prompts/generator.py crée l’environnement avec le bac à sable désactivé :

# rag/prompts/generator.py — environnement sans sandbox
PROMPT_JINJA_ENV = jinja2.Environment(autoescape=False, trim_blocks=True, lstrip_blocks=True)

Une fonction utilitaire rend ensuite un texte fourni par l’utilisateur à travers cet environnement :

def citation_prompt(user_defined_prompts: dict = {}) -> str:
    template = PROMPT_JINJA_ENV.from_string(
        user_defined_prompts.get("citation_guidelines", CITATION_PROMPT_TEMPLATE))
    return template.render()

La valeur citation_guidelines n’est pas une constante de développeur. Elle est extraite d’une balise XML <CITATION_GUIDELINES> à l’intérieur du sys_prompt du composant LLM — un paramètre que l’utilisateur contrôle entièrement via le DSL de workflow Canvas de RAGFlow. Comme RAGFlow active l’auto-inscription par défaut et laisse tout utilisateur connecté enregistrer un Canvas arbitraire, un compte normal, à faibles privilèges, suffit.

Le rendu ne se déclenche que lorsque la citation est active (cite=True, valeur par défaut) et qu’il existe des fragments récupérés. La chaîne du chercheur fournit ces fragments via un nœud de recherche DuckDuckGo, donc aucun modèle d’embedding et aucune clé d’API de fournisseur ne sont nécessaires. Résultat : une chaîne placée dans un champ de prompt est évaluée côté serveur, et une expression Jinja2 qui parcourt le graphe d’objets de Python atteint os et exécute une commande shell dans le processus RAGFlow. La preuve de concept de l’avis public écrit la sortie de id sur disque pour confirmer l’exécution — dans son essai en laboratoire, en tant que root.

Le payload lui-même est l’idiome classique d’évasion de sandbox SSTI Jinja2 (parcours de __globals__ / __builtins__ pour importer os) ; nous ne reproduisons donc pas de ligne fonctionnelle ici — la leçon est le motif, pas la chaîne. Un avis frère, GHSA-vvwj-fvwh-4whx, signale la même classe de SSTI dans le composant de traitement de texte de l’agent RAGFlow, ce qui suggère un problème systémique plutôt qu’un cas isolé.

Pourquoi c’est important

Un serveur RAG est une cible de grande valeur. Il détient généralement des clés d’API de fournisseurs, des identifiants de base de données et les documents ingérés de l’organisation — exactement ce qu’un attaquant convoite. Transformer un champ de prompt en libre-service en RCE sur l’hôte fait s’effondrer toute la frontière de confiance : un utilisateur à faibles privilèges (ou quiconque peut s’inscrire) obtient l’exécution de code dans le contexte du serveur, et de là accède à ses secrets et à sa position réseau.

Le point de fond dépasse largement RAGFlow. Les modèles de prompt sont du code, et l’entrée utilisateur ne doit jamais être le template. Beaucoup de produits LLM traitent system_prompt, citation_guidelines ou les champs de persona comme du texte inoffensif et les passent à Jinja2, à des f-strings ou à format() pour « remplir des variables ». Dès l’instant où du contenu non fiable peut définir le template plutôt que les valeurs, vous avez une SSTI — le même bug que les frameworks web ont appris à connaître il y a une décennie, qui revient désormais par la couche d’outillage IA.

Défenses

Ne jamais rendre une entrée non fiable comme un template. Traitez les champs de prompt contrôlés par l’utilisateur strictement comme des données : passez-les en variables de rendu (template.render(guidelines=user_text)), jamais comme source du template. Si vous devez accepter des fragments de template, cette entrée est privilégiée et doit être protégée en conséquence.

Si vous templatez, mettez-le en bac à sable. Utilisez SandboxedEnvironment (ou ImmutableSandboxedEnvironment) de Jinja2, qui bloque l’accès aux attributs dunder et aux appels dangereux. Un Environment().from_string(user_input) non sandboxé est une primitive de RCE, pas un confort.

Isolez le worker. Exécutez le processus RAG/agent en tant qu’utilisateur non privilégié dans un conteneur sans réseau sortant par défaut, avec un système de fichiers en lecture seule et des capabilities retirées, afin qu’une injection réussie ne puisse atteindre ni les endpoints de métadonnées, ni les identifiants, ni le réseau étendu.

Réduisez la surface d’inscription et de DSL. Désactivez l’auto-inscription ouverte sur les instances accessibles depuis Internet, exigez un vrai privilège pour définir ou modifier le DSL de workflow, et mettez en liste blanche les composants autorisés au lieu de charger des graphes arbitraires.

Patchez et inventoriez. RAGFlow ≤ 0.24.0 est affecté ; suivez les avis du projet et passez à la dernière version corrigée. Puis recensez vos instances exposées — un moteur RAG ne devrait pas se trouver sur l’Internet public avec l’inscription par défaut activée.

Statut

ÉlémentDétail
CVECVE-2026-45312 (GHSA-wpg4-h5g2-jxm6)
AffectéRAGFlow ≤ 0.24.0
Sévérité9.9 CVSS v3.1 ; CWE-1336 (SSTI)
Authentification requiseOui — tout utilisateur à faibles privilèges (auto-inscription active par défaut)
VecteurSSTI Jinja2 dans citation_prompt() via le DSL Canvas → RCE
Signalé parYuu (anzuukino), VNUHCM-UIT / Verichains
Avis publié9 mai 2026 (CVE au NVD le 29 mai 2026)
LiéGHSA-vvwj-fvwh-4whx (SSTI dans le composant de traitement de texte de l’agent)

Sources