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
cve-2026-69243-poc-aiohttp-smuggling — Reproduces aiohttp CWE-444 request smuggling via rejected WebSocket upgrades, with Python/Rust payloads and Docker lab demonstrating proxy access-control bypass. | Kitploit
Tools/GitHubGitHub/jvbotelho/cve-2026-69243-poc-aiohttp-smuggling
Payload GenerationVulnerability AnalysisExploitationWeb Application ExploitationWeb SecurityFuzzing
GitHubjvbotelho/cve-2026-69243-poc-aiohttp-smuggling

cve-2026-69243-poc-aiohttp-smuggling

Reproduces aiohttp CWE-444 request smuggling via rejected WebSocket upgrades, with Python/Rust payloads and Docker lab demonstrating proxy access-control bypass.

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
View RepositoryWebsite
161 month agoNot yet reviewed
Share

CVE-2026-69243 — aiohttp Request Smuggling (CWE-444)

Request smuggling via rejected WebSocket upgrade in aiohttp < 3.14.2. When a reverse proxy forwards Connection: Upgrade + Upgrade: websocket headers, the vulnerable aiohttp parser skips the request body and interprets trailing bytes as a pipelined request — bypassing edge access controls.

Fixed in aiohttp 3.14.2 (commit 6ae358f).

Author: João Victor Botelho (JV Botelho) — https://glitchedcat.com

60-second run

Copy-paste the Python version (zero dependencies, stdlib only):

root@kitploit:~
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>

Or build the Rust version (zero dependencies, std only):

root@kitploit:~
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>

Prebuilt binaries will be published to Releases via the release workflow.

Both produce byte-identical payloads (enforced by a CI parity test). If the lab is running:

root@kitploit:~
python3 poc.py nginx-upgrade 80 backend-vuln

Expected output: 1 HTTP response (WebSocket upgrade rejected). Then verify:

root@kitploit:~
# 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'

The backend count > 0 and Nginx count = 0 means CWE-444 split confirmed. This PoC sends a single TCP segment — the body is the smuggled request; Nginx treats it as body, aiohttp treats it as a second request.

What this proves

  • Parser confusion: aiohttp 3.14.1 _http_parser.pyx returns 2 (skip body) on upgrade detection before the body is consumed (line ~863). Body bytes remain in _message_tail and are fed back to the parser in web_protocol.py finish_response (line ~771).
  • CWE-444 split: The frontend sees 1 request with body; the backend sees 2 pipelined requests. Request-count mismatch in logs.
  • Access-control bypass: When Nginx has location /admin { deny all; }, the smuggled /admin still reaches the backend because Nginx routing decisions are made on the outer request only.
  • No handler-level fix: await request.read() returns 0 bytes on upgrade requests in 3.14.1 — the body is withheld below the handler layer. Patch, or strip upgrade headers at the proxy on routes that must not switch protocols.

Full lab

root@kitploit:~
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

Services:

ContainerPurpose
backend-vulnaiohttp 3.14.1 (vulnerable), READ_BODY=false
backend-vuln-readaiohttp 3.14.1, READ_BODY=true (proves handler can't help)
backend-patchedaiohttp 3.14.2 (fixed)
nginx-upgradeForwards upgrade headers (deny all on /admin)
nginx-defaultNo upgrade forwarding (neutralizes the bug)
nginx-stripConnection "" strip (neutralizes the bug)
attackerRust binaries: reproduce, fase2, poc

Full Phase 1-3 findings in findings/.

Limitations (honest)

  • Proxy must forward upgrade headers. Nginx configs that send Connection: close to the backend or strip Connection/Upgrade headers are not vulnerable. The config that exposes the split is the canonical WebSocket map snippet from Nginx's own proxying documentation.
  • Smuggled response is absorbed by the proxy. In the demonstrated Nginx chain, the attacker receives only the outer /ws response. Evidence of smuggling is in the backend logs, not in the attacker response — a blind, one-way primitive in this topology. Other proxy topologies were not tested.
  • Chunked framing is CL-specific in effect. Directly against aiohttp, Transfer-Encoding: chunked does NOT smuggle — the raw chunk-size line (e.g. 3e) hits the parser as an invalid method and the connection dies. Through Nginx it works, but via normalization: Nginx de-chunks the body and forwards a synthesized Content-Length, so the backend is exploited through the same CL path. The --chunked flag demonstrates the proxy path.
  • Requires an endpoint that rejects WebSocket upgrades. The handler must return a non-WebSocketResponse. Most apps that don't use WebSockets on a route will reject by default (framework returns 404 or falls through to the next handler).

Files

root@kitploit:~
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

Detection

See findings/fase3-deteccao.md for the full analysis. Summary, with the caveats that matter in production:

  1. Request-count mismatch (backend > frontend) on the same connection. Note the backend logs the proxy's IP, not the client's — correlation requires normalizing X-Forwarded-For/Proxy Protocol, or logging an upstream connection ID + per-connection request sequence. Client IP + time window alone is weak (NAT, keep-alive, concurrency).
  2. Backend request without frontend match: a restricted path in the backend log with no corresponding entry in the frontend access log. Filter internal subnets (health checks bypassing the proxy false-positive).
  3. Upgrade rejection + different path from same client in ~100 ms. Medium confidence — legitimate pipelining produces similar intervals; the lab logger does not record remote port/connection ID, so "no new TCP handshake" is NOT something this data demonstrates.
  4. Withheld-body middleware (content_length vs bytes from request.read()): fires on 3.14.1, quiet on 3.14.2. Detects, does not mitigate. Scope it (upgrade-candidate routes, small bodies, non-101 statuses) — the naive version buffers every body in memory.
  5. Edge reqlen above header baseline on WebSocket endpoints — low confidence alone (cookies/JWT/tracing headers are noisy), but the only edge signal when the client sends no Content-Length (chunked ingress).
Download Tool