A public HTTP endpoint that hands attacker-supplied Python straight to exec() with no auth check
Langflow is a popular low-code builder for LLM/agent workflows. In versions prior to 1.3.0, the /api/v1/validate/code endpoint takes user-submitted Python source, parses it into an AST, and invokes exec() on the result — without authentication and without a sandbox. Attackers embed the payload in a function decorator or default argument value, both of which evaluate at definition time, so the malicious code runs the instant the endpoint compiles it. A single unauthenticated POST yields command execution as the Langflow process user.
The vendor's CRITICAL / 9.8 rating is entirely accurate and, if anything, understated by recent history. CVSS captures the raw primitive; it doesn't capture that Langflow is routinely deployed on internet-facing hosts by data scientists who never intended it to be a hardened production service. AV:N/AC:L/PR:N/UI:N is literal — a curl one-liner is enough.
4 steps from start to impact.
Discover an exposed Langflow instance
/api/v1/version response. Mass scanners have been observed spraying the endpoint indiscriminately since April 2025.- Langflow instance reachable from attacker network position (usually internet, but any routable path works)
- Instances behind a VPN, IdP-fronted reverse proxy, or private VPC are not directly reachable — but internal reachability is still enough for lateral movement
product:Langflow and Censys services.http.response.html_title: Langflow both index thousands of hits.Craft a decorator/default-argument payload
@__import__('os').system('...')) or a default arg (def f(x=__import__('os').system('...')):). When Langflow AST-compiles it via exec(), Python evaluates these positions immediately, executing the attacker's command before any "validation" logic runs. Public PoCs from ill-deed/Langflow-CVE-2025-3248-Multi-target and Horizon3 automate this.- Understanding of Python decorator/default-arg evaluation timing
- None — the primitive is a textbook eval-injection and the PoC is a one-page script
validate/code combined with __import__ or os.system in POST body catch nearly all in-the-wild payloads.POST to /api/v1/validate/code
/api/v1/validate/code with {"code":"..."} returns HTTP 200 and executes the payload server-side as the Langflow process user. No session, cookie, API key, or CSRF token is required.- HTTP reachability to the endpoint
- A WAF rule blocking obvious
__import__/execbodies helps briefly but is bypassable via string obfuscation, base64, orgetattrchains
POST /api/v1/validate/code gives high-signal telemetry — legitimate use is rare from external addresses.Establish persistence and pivot
- Egress network access from Langflow host
- Egress filtering to only whitelisted LLM/API destinations breaks the downloader stage but not credential/data theft from within
python -> curl|wget -> chmod +x -> exec from a webapp process tree; Langflow spawning a shell is anomalous.CurrentActiveUser dependency to the vulnerable endpoint. Because Langflow is typically pip- or Docker-installed, the upgrade is pip install --upgrade langflow or bumping the container tag. Complete within the noisgate remediation SLA of ≤90 days, but for KEV-listed bugs treat 7 days as the real deadline.__import__, os.system, subprocess in JSON bodies.curl, wget, chmod +x, /tmp/* executions, or connections to Flodrix C2s (see Trend Micro IoC list). Any hit is an incident, not a patch.- Requiring a Langflow login in the UI — the vulnerable endpoint bypasses UI auth entirely in <1.3.0; the fix had to be code-level.
- Rate limiting — a single request lands the RCE; throttling does not stop the exploit, only marginally slows mass scanning.
- Network IDS signatures alone — the payload is easily obfuscated (base64,
getattr, unicode); rely on the WAF path block, not payload matching.
The supporting signals.
| In-the-wild status | Active mass exploitation since April 2025. Trend Micro documented the Flodrix botnet campaign; Zscaler ThreatLabz and Recorded Future confirm ongoing spraying. |
|---|---|
| KEV status | Listed on CISA KEV as of 2025-05-05. Federal BOD 22-01 remediation deadline has already passed. |
| EPSS | 0.99968 (>99.9th percentile) — essentially the ceiling of exploitation probability. |
| Public PoC | Multiple public exploits: ill-deed/Langflow-CVE-2025-3248-Multi-target (mass-scan variant), Horizon3.ai writeup with working payload, SSD-Disclosure advisory. Metasploit-style one-liners circulating. |
| CVSS vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H — network, no auth, no user interaction, full CIA impact. Vector is accurate. |
| Affected versions | Langflow < 1.3.0 — all releases from initial public release through 1.2.x. |
| Fixed version | 1.3.0 and later. Fix adds _current_user: CurrentActiveUser dependency to post_validate_code, requiring auth. |
| Exposure data | Shodan/Censys have consistently indexed 500–1,500 internet-exposed Langflow instances through 2025-2026. GreyNoise tag langflow-validate-code-rce-attempt shows daily scan volume. |
| Disclosure | GHSA-vwmf-pq79-vjvx published 2025-04-07. Reported by Horizon3.ai. |
| CWE | CWE-306 Missing Authentication for Critical Function (compounded by CWE-94 Code Injection). |
Sources.
- GitHub Security Advisory GHSA-vwmf-pq79-vjvx
- NVD — CVE-2025-3248
- Trend Micro — Langflow / Flodrix botnet campaign
- Zscaler ThreatLabz — CVE-2025-3248 RCE analysis
- Recorded Future — Active exploitation report
- Public PoC — ill-deed/Langflow-CVE-2025-3248-Multi-target
- SentinelOne vulnerability database entry
- Miggo vulnerability writeup
Why this verdict
- KEV + EPSS 0.99968: listed since 2025-05-05 and actively distributing the Flodrix botnet — exploitation is not theoretical, it is occurring today at scan-and-spray scale.
- Zero prerequisites: unauthenticated, no user interaction, single HTTP POST. There is no friction lever to lean on.
- Role multiplier — AI workflow orchestrator: Langflow instances commonly hold OpenAI/Anthropic/Bedrock keys, connect to vector databases with proprietary embeddings, and increasingly front internal MCP servers. RCE here yields both the host and the AI supply-chain keys. Floor = CRITICAL.
- Shadow-IT prevalence: data-science teams stand these up on cloud VMs outside standard patch pipelines; CMDB coverage is poor, meaning defenders often don't know they own an instance until it's popped.
Why not higher?
CRITICAL is the top severity bucket in this schema. The only reason to escalate operationally beyond the 3-day mitigation SLA is if you know you have exposed instances — in which case treat it as an active incident, not a patch cycle.
Why not lower?
There is no defensible path to HIGH. The bug is unauthenticated, remotely triggerable in a single request, KEV-listed, has a botnet operator behind it, and lives in a component whose deployment context routinely holds high-value secrets. Every downgrade lever noisgate would normally apply is absent.
Crowdsourced verification payload.
Run this from an auditor workstation with network reachability to the target Langflow instance. No credentials needed. Usage: ./check_langflow.sh https://langflow.internal.example.com — the script fetches the version endpoint and, if unreachable, attempts a benign echo payload against /api/v1/validate/code to confirm the vulnerable code path.
#!/usr/bin/env bash
# noisgate — CVE-2025-3248 Langflow unauthenticated RCE check
# Usage: ./check_langflow.sh <base_url>
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN
set -u
TARGET="${1:-}"
if [[ -z "$TARGET" ]]; then
echo "Usage: $0 https://host:port" >&2
exit 2
fi
# Strip trailing slash
TARGET="${TARGET%/}"
# 1. Try to read version from the API
VER_JSON="$(curl -sk --max-time 10 "$TARGET/api/v1/version" || true)"
VERSION="$(echo "$VER_JSON" | grep -oE '"version"[[:space:]]*:[[:space:]]*"[^"]+"' | head -1 | sed -E 's/.*"([^"]+)"$/\1/')"
if [[ -n "$VERSION" ]]; then
# semver compare vs 1.3.0
IFS='.' read -r MAJ MIN PAT <<<"$(echo "$VERSION" | sed -E 's/[^0-9.].*//')"
MAJ=${MAJ:-0}; MIN=${MIN:-0}; PAT=${PAT:-0}
if (( MAJ > 1 )) || { (( MAJ == 1 )) && (( MIN >= 3 )); }; then
echo "PATCHED Langflow version $VERSION (>= 1.3.0)"
exit 0
else
echo "VULNERABLE Langflow version $VERSION (< 1.3.0)"
exit 1
fi
fi
# 2. Fallback: probe endpoint with a NON-DESTRUCTIVE payload.
# The payload only defines a decorator that raises during import so we
# distinguish "endpoint exists and eval'd" from "endpoint absent".
PROBE='{"code":"def _n():\n pass"}'
HTTP_CODE="$(curl -sk --max-time 10 -o /tmp/lf_probe.$$ -w '%{http_code}' \
-H 'Content-Type: application/json' \
-X POST "$TARGET/api/v1/validate/code" -d "$PROBE" || echo 000)"
BODY="$(cat /tmp/lf_probe.$$ 2>/dev/null || true)"
rm -f /tmp/lf_probe.$$
case "$HTTP_CODE" in
200)
if echo "$BODY" | grep -q '"imports"\|"function"'; then
echo "VULNERABLE /api/v1/validate/code reachable unauthenticated (HTTP 200)"
exit 1
fi
echo "UNKNOWN HTTP 200 but unexpected body; inspect manually"
exit 2
;;
401|403)
echo "PATCHED Endpoint requires authentication (HTTP $HTTP_CODE)"
exit 0
;;
404)
echo "UNKNOWN Endpoint not found — not a Langflow host or non-standard path"
exit 2
;;
*)
echo "UNKNOWN HTTP $HTTP_CODE from probe"
exit 2
;;
esac