/ root / blog / request_smuggling

Zrozumieć Request Smuggling

[--date:2025-02-16] [--tag:Teoria] [--tag:WEB]

Wstęp

Podczas przygotowań do egzaminu BSCP najbardziej wymagającym i najmniej znanym mi typem podatności był HTTP request smuggling.

Aby w pełni zrozumieć ten atak, należy poznać podstawy protokołu HTTP oraz architekturę nowoczesnych aplikacji internetowych, zwłaszcza w kontekście serwerów proxy, load balancerów i backendu. W tym artykule poszerzę swoją wiedzę, zgłębiając każdy aspekt tej podatności.

Dla osób nieznających tego ataku: request smuggling wykorzystuje różnice w sposobie parsowania żądań HTTP między serwerami frontendowymi i backendowymi. Celem jest manipulacja granicami żądania, co często sprawia, że jedno żądanie jest interpretowane jako dwa oddzielne. Jak to się robi? Dlaczego tak się dzieje? Spróbuję odpowiedzieć na te pytania.

Skutki request smuggling różnią się w zależności od infrastruktury aplikacji i zabezpieczeń. W niektórych przypadkach atak ten pozwala na ominięcie uwierzytelniania, dostęp do zastrzeżonych zasobów lub przejęcie sesji użytkowników, co stanowi poważne zagrożenie.

Kluczowe nagłówki HTTP w Request Smuggling

Przed przejściem do technik ataku warto zrozumieć nagłówki, które odgrywają kluczową rolę w parsowaniu żądań HTTP.

Kiedy wprowadzono te nagłówki i jakie problemy rozwiązywały? HTTP/1.1 został wprowadzony w 1997 roku, aby rozwiązać część problemów wersji 1.0. Głównym problemem HTTP/1.0 była jego nieefektywność – każde żądanie wymagało pełnego 3-etapowego nawiązania połączenia (3-way handshake) przed jego przetworzeniem. Dodatkowo brakowało wbudowanej obsługi stałych połączeń i mechanizmów buforowania. Jednym z rozwiązań w HTTP/1.1 był nagłówek Transfer-Encoding, który poprawił wydajność przesyłania danych.

Connection i Keep-Alive

Podczas gdy nagłówki Content-Length i Transfer-Encoding określają długość wiadomości, Connection i Keep-Alive pozwalają na utrzymanie otwartego połączenia dla wielu żądań, co redukuje potrzebę nawiązywania nowego połączenia dla każdego z nich.

Nagłówek Connection ma dwie główne opcje:

  • keep-alive (domyślnie w HTTP/1.1): Utrzymuje otwarte połączenie dla kolejnych żądań.
  • close: Zamyka połączenie po wysłaniu odpowiedzi.

Nagłówek Keep-Alive określa liczbę żądań dozwolonych przed zamknięciem połączenia oraz czas, przez jaki nieaktywne połączenie pozostaje otwarte. Przykład:

Keep-Alive: timeout=5, max=200

Oznacza to, że połączenie pozostanie otwarte do 5 sekund i obsłuży do 200 żądań przed zamknięciem.

Content-Length

Jaki jest format Content-Length i do czego służy?

Zgodnie z RFC 9110, Content-Length określa długość ciała wiadomości jako dziesiętną, nieujemną liczbę całkowitą (mierzoną w oktetach).

Dlaczego jest to konieczne?

  • Pozwala odbiorcom określić, kiedy wiadomość jest kompletna i śledzić postęp jej przesyłania.

Ważny szczegół z RFC 9112 mówi, że:

  • Jeśli wiadomość nie zawiera nagłówka Transfer-Encoding, wtedy Content-Length definiuje jej długość.

  • Nadawca NIE MOŻE dołączać nagłówka Content-Length do wiadomości, która zawiera już nagłówek Transfer-Encoding.

Transfer-Encoding

Jak określono w RFC 9112, nagłówek Transfer-Encoding definiuje, w jaki sposób ciało wiadomości jest kodowane do transferu. Najpopularniejszą wartością jest kodowanie kawałkowane (chunked). Jak działa kodowanie chunked? - Ciało żądania jest wysyłane w postaci serii fragmentów (chunków).

  • Każdy fragment zaczyna się od jego rozmiaru zapisanego w systemie szesnastkowym, po którym następują właściwe dane.

  • Fragment o rozmiarze 0 sygnalizuje koniec wiadomości.

Kodowanie to umożliwia:

  • Stopniowe przetwarzanie (odbiorcy mogą zacząć przetwarzać dane przed otrzymaniem całej wiadomości).

  • Dynamiczne przesyłanie treści (przydatne, gdy końcowy rozmiar treści nie jest znany).

  • Stałe połączenia (keep-alive), co poprawia wydajność protokołu HTTP.

Obsługa Transfer-Encoding i Content-Length w HTTP

Mimo że specyfikacja zabrania nadawcy dodawania nagłówka Content-Length do wiadomości zawierającej nagłówek Transfer-Encoding, w praktyce nie zawsze jest to rygorystycznie sprawdzane. Problem ten rozwiązano w HTTP/2, gdzie Transfer-Encoding nie jest już używany.

Co się stanie, jeśli oba nagłówki są obecne w żądaniu?

Sekcja 6.3 dokumentu RFC 9112 omawia potencjalny scenariusz ataku i dostarcza wskazówek, jak sobie z nim radzić. Punkt 3 mówi:

“Jeśli otrzymana wiadomość zawiera zarówno pole nagłówka Transfer-Encoding, jak i Content-Length, Transfer-Encoding nadpisuje Content-Length. Taka wiadomość może wskazywać na próbę ataku request smuggling (Sekcja 11.2) lub response splitting (Sekcja 11.1) i powinna być traktowana jako błąd. Serwer pośredniczący, który zdecyduje się przesłać wiadomość dalej, MUSI najpierw usunąć pole Content-Length i przetworzyć Transfer-Encoding przed przekazaniem wiadomości dalej.”

Zatem specyfikacja wymaga, aby serwery były świadome obu nagłówków. Jeśli występują oba, nagłówek Content-Length powinien zostać usunięty, by zapobiec błędnej interpretacji.

Dlaczego jest to problemem bezpieczeństwa?

Kiedy tworzono specyfikację HTTP w 1997 roku, infrastruktura była prostsza, a większość stron obsługiwał pojedynczy serwer. Nowoczesne architektury opierają się na wielu komponentach, takich jak proxy, load balancery i bramy bezpieczeństwa (security gateways). Niektóre z nich nie wspierają w pełni Transfer-Encoding lub mogą różnie interpretować sprzeczne nagłówki.

Ta niekonsekwencja w obsłudze nagłówków przez różne serwery stwarza okazję do ataków HTTP request smuggling. Atakujący manipuluje nagłówkami, by serwer zinterpretował żądania inaczej niż kolejne komponenty w łańcuchu. Może to prowadzić do poważnych naruszeń bezpieczeństwa, takich jak zatrucie pamięci podręcznej (cache poisoning), nieautoryzowane wykonanie żądań czy ominięcie mechanizmów uwierzytelniania.

Identyfikacja

Co wiemy na tym etapie? Aby wystąpiła ta podatność, w łańcuchu żądań musi znajdować się wiele serwerów, które niekonsekwentnie obsługują nagłówki.

Pierwszym krokiem jest identyfikacja serwerów proxy za pomocą narzędzi takich jak traceroute lub nslookup. Dodatkowo obecność nagłówków takich jak Via lub X-Forwarded-For może wskazywać na architekturę z wieloma przeskokami (proxy, load balancery).

W Burp Suite rozszerzenie HTTP Request Smuggler może automatycznie wysyłać spreparowane żądania w celu wykrycia podatności. Ale co dzieje się pod spodem?

Podstawowe techniki ataku

Zanim przejdziemy dalej, omówmy trzy główne typy ataków HTTP request smuggling:

  • CL.TE (Konflikt Content-Length i Transfer-Encoding) Serwer frontendowy przetwarza Content-Length (CL), podczas gdy backendowy przetwarza Transfer-Encoding (TE).

  • TE.CL (Konflikt Transfer-Encoding i Content-Length) Odwrotność powyższego: frontend przetwarza Transfer-Encoding, a backend polega na Content-Length.

  • TE.TE (Dwa sprzeczne nagłówki Transfer-Encoding) Różne serwery w łańcuchu inaczej interpretują zduplikowane lub zaciemnione nagłówki Transfer-Encoding.

Istnieją pewne wymagania do identyfikacji tych podatności:

  1. Powierzchnia ataku dotyczy tylko HTTP/1.1.

  2. W Burp Suite przejdź do Repeatera, zmień wersję HTTP na 1.1 i sprawdź, czy żądanie jest obsługiwane.

  3. Włącz opcję wyświetlania znaków niedrukowalnych. Zobaczysz \r\n (Carriage Return + Line Feed) na końcu każdej linii. HTTP opiera się na starych standardach Telnet/MIME, gdzie \r\n oznacza początek nowej linii. Dodatkowe \r\n po nagłówkach oznacza przejście między nagłówkami a ciałem żądania.

Zrozumienie kodowania Chunked

Przed rozpoczęciem testów warto zrozumieć format kodowania chunked. Według specyfikacji HTTP ciało wiadomości wygląda następująco:

chunked-body   = *chunk
                 last-chunk
                 trailer-section
                 CRLF

chunk          = chunk-size CRLF
                 chunk-data CRLF

Rozmiar fragmentu (chunk-size) to wartość szesnastkowa określająca długość danych. Sekcja zwiastunów (trailer-section) może zawierać dodatkowe nagłówki, ale na potrzeby testów je zignorujemy. Prawidłowo sformatowane ciało wygląda tak:

3\r\n
abc\r\n
0\r\n
\r\n

Pierwszy fragment o rozmiarze 3 przesyła wartość abc, a kolejny fragment o rozmiarze 0 wraz ze znakiem nowej linii informuje serwer, że to już koniec.

Przejdźmy teraz do testowania.

Identyfikacja podatności* Aby przetestować aplikację pod kątem request smuggling, wyślij poniższe żądanie:

Content-Length: 6
Transfer-Encoding: chunked
\r\n
3\r\n
abc\r\n
X\r\n
\r\n

Co tu się dzieje: Ciało wiadomości ma 13 bajtów, ale nagłówek Content-Length: 6 mówi serwerowi, aby spodziewał się tylko 6 bajtów. Obecność X po abc może ujawnić niespójność w interpretacji żądania przez frontend i backend.

Możliwe odpowiedzi serwera:

  1. Natychmiastowe odrzucenie: Frontend prawidłowo rozpoznaje Transfer-Encoding: chunked i traktuje X jako nieprawidłowy rozmiar fragmentu (nie jest to wartość szesnastkowa). To sugeruje, że serwer poprawnie przetwarza TE. Nie wiemy jednak, jak zareaguje backend. Może to być sytuacja TE.CL lub TE.TE.

  2. Prawidłowa odpowiedź: Oznacza to, że zarówno frontend, jak i backend opierają się na Content-Length, co czyni aplikację odporną na request smuggling.

  3. Timeout (przekroczenie czasu oczekiwania): Backend ignoruje Content-Length, interpretuje abc jako fragment, ale zatrzymuje się na X, ponieważ nie jest to prawidłowy rozmiar. Backend czeka w nieskończoność na prawidłowy rozmiar fragmentu, który nigdy nie nadchodzi. Wskazuje to na podatność TE.CL.

Potwierdzenie TE.TE lub TE.CL

Aby sprawdzić, czy aplikacja jest podatna na TE.TE czy TE.CL, wyślij drugie żądanie:

Content-Length: 6
Transfer-Encoding: chunked
\r\n
0\r\n
\r\n
X

Możliwe odpowiedzi serwera

  1. Prawidłowa odpowiedź: Sugeruje to obsługę TE.TE lub CL.CL.

  2. Zachowanie CL.TE: Frontend przesyła całe ciało, ale backend zostawia X w buforze. Wysłanie kolejnego żądania może spowodować błąd nieprawidłowej metody (np. XPOST), co udowadnia, że X zostało potraktowane jako początek następnego żądania.

  3. Timeout: Frontend ignoruje Content-Length, co oznacza, że X nigdy nie jest wysyłane. Backend oczekuje ciała o długości 6, ale go nie otrzymuje. Potwierdza to podatność TE.CL.

Automatyczna identyfikacja

Pokazałem, jak ręcznie testować aplikacje za pomocą tych dwóch żądań. Można to jednak zautomatyzować rozszerzeniem HTTP Request Smuggler. Aby je uruchomić, kliknij prawym przyciskiem myszy na żądanie i wybierz Extensions -> HTTP Request Smuggler -> Smuggle Probe. W zakładce Target otrzymałem komunikat:

Poniżej znajduje się jedno z wygenerowanych żądań, podobne do tych pokazanych wcześniej:

“Possible HTTP Request Smuggling: CL.TE multiCase (delayed response).”

Poniżej znajduje się jedno z wygenerowanych żądań, podobne do tych pokazanych wcześniej:

Content-Length: 13
tRANSFER-ENCODING: chunked
\r\n
3\r\n
x=y\r\n
1\r\n
Z\r\n
Q\r\n
\r\n

Odpowiedzią na to żądanie jest timeout. Dlaczego sugeruje to CL.TE? Podobnie jak w poprzednim przypadku, frontend całkowicie ignoruje TE, ale backend już nie. Gdy otrzymuje Q jako rozmiar fragmentu, czeka na prawidłową wartość i odpowiada timeoutem.

Content-Length: 14
tRANSFER-ENCODING: chunked
\r\n
3\r\n
x=y\r\n
0\r\n
\r\n
X

Tym razem następuje automatyczna detekcja TE.CL. Podejście jest bardzo podobne. Frontend ignoruje X, ponieważ korzysta z TE i poprawnie wykrywa ostatni fragment. Backend czeka jednak na czternasty bajt na podstawie wartości CL, ale nigdy go nie otrzymuje, co skutkuje timeoutem.

Wykrywanie TE.TE

Jak pokazano wyżej, TE.TE można skutecznie wykryć za pomocą dwóch pierwszych żądań. Taka sytuacja występuje, gdy zarówno backend, jak i frontend używają TE, a my możemy zaciemnić nagłówki, aby zmusić jeden z serwerów do ich ignorowania. Jak wyjaśniono w Akademii PortSwigger:

“Każda z tych technik opiera się na subtelnym odstępstwie od specyfikacji HTTP. Prawdziwy kod implementujący protokół rzadko trzyma się go z absolutną precyzją i często zdarza się, że różne implementacje tolerują odmienne odstępstwa od standardu.”

Rolą atakującego jest więc znalezienie tych subtelnych różnic, które sprawią, że nagłówek zostanie zignorowany. W powyższym przykładzie z rozszerzenia użyto obfuskacji zmieniającej wielkość liter: tRANSFER-ENCODING: chunked. Narzędzie próbuje wykonać wiele identyfikacji jednocześnie, łącząc różne techniki w jednym żądaniu.

Istnieje wiele technik zaciemniania nagłówka TE. Oto kilka z nich:

Transfer-Encoding: xchunked

Transfer-Encoding: x

Transfer-Encoding:[tab]chunked

[space]Transfer-Encoding: chunked

X: X[\n]Transfer-Encoding: chunked

Transfer-Encoding
: chunked

Transfer-encoding: identity

Transfer-encoding: cow

tRANSFER-ENCODING: chunked

HTTP/2 Request Smuggling

Chociaż HTTP/2 zachowuje te same metody, kody statusu i nagłówki co jego poprzednik, zasadniczo zmienia sposób formatowania i przesyłania danych. W przeciwieństwie do tekstowego HTTP/1, HTTP/2 jest protokołem binarnym. W kontekście request smuggling kluczowe jest to, że HTTP/2 nie używa nagłówków Transfer-Encoding ani Content-Length w ten sam sposób. Zamiast tego każda ramka na samym początku określa swoją długość w bajtach, co definiuje format ramki HTTP/2

Downgrade z HTTP/2

Podatność HTTP/2 na request smuggling wynika głównie z procesu degradacji (downgradingu) z HTTP/2 do HTTP/1.1. Wiele firm wdraża go ze względu na wsteczną kompatybilność ze starszymi systemami backendowymi. Pozwala to na korzystanie z nowoczesnych rozwiązań frontendowych przy zachowaniu dostępu do starych systemów.

Taka elastyczność niesie jednak ryzyko. Podczas downgradingu backend traci informację o długości ramki HTTP/2, co potencjalnie umożliwia wstrzyknięcie nagłówków HTTP/1.

H2.CL and H2.TE

W HTTP/2 żądania korzystają z pseudonagłówków zamiast tradycyjnej linii żądania. Na przykład ścieżka jest reprezentowana przez pseudonagłówek :path, a nagłówek Host zastępuje :authority. Oto przykład próby przemycenia żądania:

:method POST
:path /test
:authority example.com
content-type application/x-www-form-urlencoded
transfer-encoding chunked

0

GET /admin HTTP/1.1
Host: example.com
Foo: Injected

Po degradacji do HTTP/1.1, to żądanie może wyglądać tak:

POST /test HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Transfer-Encoding: chunked

0

GET /admin HTTP/1.1
Host: example.com
Foo: Injected

W tym scenariuszu backend może potraktować ciało jako fragment o zerowej długości, a resztę zinterpretować jako początek nowego żądania. Może to umożliwić dostęp do zastrzeżonych ścieżek, takich jak /admin.

Podobnie, atakujący mógłby spróbować wstrzyknąć nagłówek Content-Length:

:method POST
:path /
:authority example.com
content-type application/x-www-form-urlencoded
content-length 0

GET /admin HTTP/1.1
Host: example.com

Po downgradzie wygląda to tak:

POST / HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 0

GET /admin HTTP/1.1
Host: example.com

Tutaj ciało żądania może zostać zignorowane ze względu na nagłówek Content-Length: 0, co pozwala na przetworzenie przemyconego żądania.

Istnieje wiele potencjalnych punktów wstrzyknięć do przetestowania. Świetny artykuł na ten temat napisał James Kettle: HTTP/2: The Sequel is Always Worse. W tym wpisie chciałem jedynie zarysować podstawowy mechanizm.

Podsumowanie

Zbudowaliśmy solidne fundamenty do zrozumienia HTTP request smuggling. Przeanalizowaliśmy kluczowe nagłówki HTTP, sprawdziliśmy jak niespójności między frontendem a backendem mogą prowadzić do luk w zabezpieczeniach oraz poznaliśmy ręczne i automatyczne metody ich wykrywania. Kluczowe jest zrozumienie roli nagłówków Content-Length i Transfer-Encoding w obsłudze żądań.

Zrozumienie request smuggling w HTTP/2 wymaga znajomości różnic między nim a HTTP/1, zwłaszcza w formatowaniu i przesyłaniu danych. Podatność ta wynika głównie z procesu degradacji (downgradingu) dla zachowania kompatybilności wstecznej. Manipulując nagłówkami i wykorzystując różnice w interpretacji żądań przez poszczególne serwery, atakujący mogą przemycać żądania również w środowiskach opartych na HTTP/2.