GHSA-vwmf-pq79-vjvx: Unauthenticated Remote Code Execution in Langflow via Public Flow Build Endpoint
SummaryThe `POST /api/v1/build_public_tmp/{flow_id}/flow` endpoint allows building public flows without requiring authentication. When the optional `data` parameter is supplied, the endpoint uses **attacker-controlled flow data** (containing arbitrary Python code in node definitions) instead of the stored flow data from the database. This code is passed to `exec()` with zero sandboxing, resulting in unauthenticated remote code execution.This is distinct from CVE-2025-3248, which fixed `/api/v1/validate/code` by adding authentication. The `build_public_tmp` endpoint is **designed** to be unauthenticated (for public flows) but incorrectly accepts attacker-supplied flow data containing arbitrary executable code.Affected CodeVulnerable Endpoint (No Authentication)**File:** `src/backend/base/langflow/api/v1/chat.py`, lines 580-657Compare with the authenticated build endpoint at line 138, which requires `current_user: CurrentActiveUser`.Code Execution ChainWhen attacker-supplied `data` is provided, it flows through:`start_flow_build(data=attacker_data)` → `generate_flow_events()` -- `build.py:81``create_graph()` → `build_graph_from_data(payload=data.model_dump())` -- `build.py:298``Graph.from_payload(payload)` parses attacker nodes -- `base.py:1168``add_nodes_and_edges()` → `initialize()` → `_build_graph()` -- `base.py:270,527``_instantiate_components_in_vertices()` iterates nodes -- `base.py:1323``vertex.instantiate_component()` → `instantiate_class(vertex)` -- `loading.py:28``code = custom_params.pop("code")` extracts attacker code -- `loading.py:43``eval_custom_component_code(code)` → `create_class(code, class_name)` -- `eval.py:9``prepare_global_scope(module)` -- `validate.py:323``exec(compiled_code, exec_globals)` -- **ARBITRARY CODE EXECUTION** -- `validate.py:397`Unsandboxed exec() in prepare_global_scope**File:** `src/lfx/src/lfx/custom/validate.py`, lines 340-397**Critical detail:** `prepare_global_scope` executes `ast.Assign` nodes. An attacker's code like `_x = os.system("id")` is an assignment and will be executed during graph building -- before the flow even "runs."PrerequisitesTarget Langflow instance has at least **one public flow** (common for demos, chatbots, shared workflows)Attacker knows the public flow's UUID (discoverable via shared links/URLs)No authentication required -- only a `client_id` cookie (any arbitrary string value)When `AUTO_LOGIN=true` (the **default**), all prerequisites can be met by an unauthenticated attacker:`GET /api/v1/auto_login` → obtain superuser token`POST /api/v1/flows/` → create a public flowExploit via `build_public_tmp` without any authProof of ConceptTested Against**Langflow version 1.7.3** (latest stable release, installed via `pip install langflow`)**Fully reproducible**: 6/6 runs confirmed RCE (two sets of 3 runs each)Step 1: Obtain a Public Flow ID(In a real attack, the attacker discovers this via shared links. For the PoC, we create one via AUTO_LOGIN.)Step 2: Exploit -- Unauthenticated RCEStep 3: Verify Code ExecutionActual Test ResultsImpact**Unauthenticated Remote Code Execution** with full server process privileges**Complete server compromise**: arbitrary file read/write, command execution**Environment variable exfiltration**: API keys, database credentials, cloud tokens (confirmed in PoC: env_keys exfiltrated)**Reverse shell access** for persistent access**Lateral movement** within the network**Data exfiltration** from all flows, messages, and stored credentials in the databaseComparison with CVE-2025-3248| Aspect | CVE-2025-3248 | This Vulnerability |
|--------|--------------|-------------------|
| **Endpoint** | `/api/v1/validate/code` | `/api/v1/build_public_tmp/{id}/flow` |
| **Fix applied** | Added `Depends(get_current_active_user)` | None -- NEW vulnerability |
| **Root cause** | Missing auth on code validation | Unauthenticated endpoint accepts attacker-controlled executable code via `data` param |
| **Code execution via** | `validate_code()` → `exec()` | `create_class()` → `prepare_global_scope()` → `exec()` |
| **CISA KEV** | Yes (actively exploited) | N/A (new finding) |
| **Can simple auth fix?** | Yes (and it was fixed) | No -- endpoint is *designed* to be unauthenticated; the `data` parameter must be removed |Recommended FixImmediate (Short-term)**Remove the `data` parameter** from `build_public_tmp`. Public flows should only execute their stored flow data, never attacker-supplied data:In `generate_flow_events` → `create_graph()`, only the `build_graph_from_db` path should be reachable for unauthenticated requests:
CVSS v4.0: 9.3
CVSS v3.1: 9.8