RAGFlow CVE-2026-45312: una plantilla de prompt que ejecuta comandos del sistema
Una inyección de plantilla Jinja2 en el generador de prompts de RAGFlow convierte un campo controlado por el usuario en RCE del lado del servidor. CVSS 9.9, divulgada el 9 de mayo de 2026.
¿Qué es esto?
CVE-2026-45312 es una falla de ejecución remota de código en RAGFlow, uno de los motores de RAG (generación aumentada por recuperación) de código abierto más desplegados. Fue reportada por Yuu (VNUHCM-UIT) de Verichains y publicada en el aviso del proveedor GHSA-wpg4-h5g2-jxm6 el 9 de mayo de 2026; el registro CVE llegó al NVD el 29 de mayo de 2026. Afecta a las versiones de RAGFlow hasta la 0.24.0 inclusive, tiene un CVSS de 9.9 (AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H) y está clasificada como CWE-1336, inyección de plantilla del lado del servidor.
El error es un caso de manual de un problema que esta categoría de productos repite una y otra vez: una plantilla de prompt se renderiza como código. Un campo que parece configuración se pasa directamente a un motor de plantillas, y ese motor es Turing-completo.
Cómo funciona
RAGFlow construye algunos de sus prompts con Jinja2. El generador de prompts en rag/prompts/generator.py crea el entorno con el aislamiento desactivado:
# rag/prompts/generator.py — entorno sin sandbox
PROMPT_JINJA_ENV = jinja2.Environment(autoescape=False, trim_blocks=True, lstrip_blocks=True)
Una función auxiliar renderiza luego un texto proporcionado por el usuario a través de ese entorno:
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()
El valor citation_guidelines no es una constante del desarrollador. Se extrae de una etiqueta XML <CITATION_GUIDELINES> dentro del sys_prompt del componente LLM — un parámetro que el usuario controla por completo a través del DSL de flujos de trabajo Canvas de RAGFlow. Como RAGFlow habilita el autorregistro por defecto y permite que cualquier usuario autenticado guarde un Canvas arbitrario, basta con una cuenta normal y de bajos privilegios.
El renderizado solo se dispara cuando la citación está activa (cite=True, el valor por defecto) y existen fragmentos recuperados. La cadena del investigador aporta esos fragmentos mediante un nodo de búsqueda DuckDuckGo, por lo que no se requiere ningún modelo de embeddings ni clave de API del proveedor. El resultado: una cadena colocada en un campo de prompt se evalúa del lado del servidor, y una expresión Jinja2 que recorre el grafo de objetos de Python alcanza os y ejecuta un comando de shell en el proceso de RAGFlow. La prueba de concepto del aviso público escribe la salida de id en disco para confirmar la ejecución — en su prueba de laboratorio, como root.
El payload en sí es el modismo clásico de evasión de sandbox de SSTI en Jinja2 (recorrer __globals__ / __builtins__ para importar os), así que no reproducimos aquí una línea funcional — la lección es el patrón, no la cadena. Un aviso hermano, GHSA-vvwj-fvwh-4whx, reporta la misma clase de SSTI en el componente de procesamiento de texto del agente de RAGFlow, lo que sugiere un problema sistémico más que un caso aislado.
Por qué importa
Un servidor RAG es un objetivo de alto valor. Suele albergar claves de API de proveedores, credenciales de bases de datos y los documentos ingeridos de la organización — exactamente el material que un atacante busca. Convertir un campo de prompt de autoservicio en RCE en el host derrumba toda la frontera de confianza: un usuario de bajos privilegios (o cualquiera que pueda registrarse) consigue ejecución de código en el contexto del servidor, y desde allí accede a sus secretos y a su posición de red.
El punto de fondo va mucho más allá de RAGFlow. Las plantillas de prompt son código, y la entrada del usuario nunca debe ser la plantilla. Muchos productos LLM tratan system_prompt, citation_guidelines o los campos de persona como texto inofensivo y los pasan a Jinja2, a f-strings o a format() para «rellenar variables». En el momento en que contenido no confiable puede definir la plantilla en lugar de los valores, tienes una SSTI — el mismo error que los frameworks web aprendieron hace una década, que ahora reaparece por la capa de herramientas de IA.
Defensas
Nunca renderices entrada no confiable como plantilla. Trata los campos de prompt controlados por el usuario estrictamente como datos: pásalos como variables de renderizado (template.render(guidelines=user_text)), nunca como fuente de la plantilla. Si debes aceptar fragmentos de plantilla, esa entrada es privilegiada y debe protegerse en consecuencia.
Si usas plantillas, ponlas en sandbox. Usa SandboxedEnvironment (o ImmutableSandboxedEnvironment) de Jinja2, que bloquea el acceso a atributos dunder y a llamadas inseguras. Un Environment().from_string(user_input) sin sandbox es una primitiva de RCE, no una comodidad.
Aísla el worker. Ejecuta el proceso RAG/agente como usuario sin privilegios en un contenedor sin red saliente por defecto, con sistema de archivos de solo lectura y capabilities eliminadas, para que una inyección exitosa no pueda alcanzar endpoints de metadatos, credenciales ni la red en general.
Cierra la superficie de registro y de DSL. Desactiva el autorregistro abierto en instancias accesibles desde Internet, exige privilegios reales para definir o modificar el DSL de flujos de trabajo, y pon en lista blanca los componentes permitidos en lugar de cargar grafos arbitrarios.
Parchea e inventaría. RAGFlow ≤ 0.24.0 está afectado; sigue los avisos del proyecto y actualiza a la última versión corregida. Luego localiza tus instancias expuestas — un motor RAG no debería estar en la Internet pública con el registro por defecto habilitado.
Estado
| Elemento | Detalle |
|---|---|
| CVE | CVE-2026-45312 (GHSA-wpg4-h5g2-jxm6) |
| Afectado | RAGFlow ≤ 0.24.0 |
| Severidad | 9.9 CVSS v3.1; CWE-1336 (SSTI) |
| Autenticación requerida | Sí — cualquier usuario de bajos privilegios (autorregistro activo por defecto) |
| Vector | SSTI Jinja2 en citation_prompt() vía el DSL Canvas → RCE |
| Reportado por | Yuu (anzuukino), VNUHCM-UIT / Verichains |
| Aviso publicado | 9 de mayo de 2026 (CVE en NVD el 29 de mayo de 2026) |
| Relacionado | GHSA-vvwj-fvwh-4whx (SSTI en el componente de procesamiento de texto del agente) |