Wzmacnianie bezpieczeństwa klienta HTTP w sgcWebSockets | Blog eSeGeCe

Wzmacnianie bezpieczeństwa klienta HTTP w sgcWebSockets

· Komponenty

Pisaliśmy już wcześniej o wzmacnianiu bezpieczeństwa serwera HTTP. Ten wpis to druga połowa tej historii. Klienta traktuje się zwykle jako zaufaną stronę rozmowy, ale nią nie jest. To on wysyła Twoje klucze API, podąża za dowolnym przekierowaniem zwróconym przez zdalny host, wczytuje całą odpowiedź do pamięci i zapisuje wszystkie ciasteczka, o które odpowiedź poprosi. Wrogi lub po prostu przejęty punkt końcowy może obrócić każde z tych czterech zachowań przeciwko Tobie.

sgcWebSockets 2026.7 dodaje zestaw przełączników po stronie klienta, które zamykają każdą z tych luk. Każdy z nich jest domyślnie wyłączony, więc aktualizacja niczego nie zmienia w istniejącej aplikacji, dopóki sam nie zdecydujesz się go włączyć. Są dostępne w niskopoziomowym kliencie HTTP, w każdym gotowym kliencie REST i AI oraz w klientach HTTP/2 i HTTP/3.

Poświadczenia, które podążają za przekierowaniem

Klasyczny wyciek po stronie klienta. Wywołujesz API z nagłówkiem Authorization, serwer odpowiada kodem 302 wskazującym na inny host, a klient posłusznie powtarza żądanie tam, razem z nagłówkiem i ciasteczkami. Twój token bearer właśnie trafił do maszyny, z którą nigdy nie zamierzałeś rozmawiać. Przekierowanie nie musi być nawet złośliwe, wystarczy atakujący, który potrafi wykorzystać otwarte przekierowanie na legalnym hoście.

StripAuthOnCrossHostRedirect usuwa nagłówki Authorization i Cookie, gdy tylko przekierowanie opuszcza pierwotny host. Żądanie nadal podąża za przekierowaniem, po prostu dociera bez Twoich poświadczeń.

uses
  sgcHTTP_Client;

var
  oHTTP: TsgcIdHTTP;
begin
  oHTTP := TsgcIdHTTP.Create(nil);
  try
    oHTTP.StripAuthOnCrossHostRedirect := True;

    oHTTP.Request.CustomHeaders.Values['Authorization'] := 'Bearer ' + vToken;
    vResponse := oHTTP.Get('https://api.example.com/v1/account');
  finally
    oHTTP.Free;
  end;
end;

Przekierowania degradujące połączenie do zwykłego HTTP

Drugi problem z przekierowaniami. Zaczynasz od https://, serwer odpowiada nagłówkiem Location, który zaczyna się od http://, a klient beztrosko powtarza żądanie otwartym tekstem. Każdy po drodze może teraz odczytać treść i przepisać odpowiedź. To dokładnie ta degradacja, której HSTS ma zapobiegać w przeglądarkach, a klient HTTP nie miał dotąd żadnego odpowiednika.

NoInsecureRedirect odrzuca przekierowanie, które przeniosłoby żądanie z HTTPS do HTTP. Przekierowanie zostaje odrzucone zamiast wykonane.

oHTTP.NoInsecureRedirect := True;

// A 302 from https://api.example.com to http://api.example.com
// is now blocked instead of silently downgraded.
vResponse := oHTTP.Get('https://api.example.com/v1/data');

Oba przełączniki przekierowań są niezależne, a w praktyce chcesz mieć oba. Włącz pierwszy, a przekierowanie na inny host przestanie nieść Twój token. Włącz drugi, a przekierowanie nie będzie już mogło pozbawić Cię zabezpieczenia transportu.

Odpowiedzi bez limitu rozmiaru

Domyślny klient HTTP czyta wszystko, co serwer wyśle. Jeśli serwer wyśle treść odpowiedzi o rozmiarze dziesięciu gigabajtów albo odpowiedź fragmentowaną, w której pojedynczy fragment deklaruje długość dwóch gigabajtów, klient spróbuje przydzielić na to miejsce. To jednolinijkowa odmowa usługi wymierzona we własny proces, i nie wymaga od atakującego złamania czegokolwiek, wystarczy, że odpowie na Twoje żądanie większą ilością danych, niż się spodziewałeś.

Zamykają to dwa limity. MaxResponseSize ogranicza całkowity rozmiar treści odpowiedzi, a MaxChunkSize ogranicza pojedynczy fragment odpowiedzi fragmentowanej. Oba przyjmują zero jako "bez limitu", co jest wartością domyślną, więc nic się nie zmienia, dopóki ich nie ustawisz. Po przekroczeniu limitu odczyt zostaje przerwany i zgłaszany jest wyjątek, klient nie powiększa dalej bufora.

uses
  sgcHTTP_Client;

var
  oHTTP: TsgcIdHTTP;
begin
  oHTTP := TsgcIdHTTP.Create(nil);
  try
    oHTTP.MaxResponseSize := 10 * 1024 * 1024;  // 10 MB total, 0 = unlimited
    oHTTP.MaxChunkSize    := 1024 * 1024;       // 1 MB per chunk, 0 = unlimited

    vResponse := oHTTP.Get('https://api.example.com/v1/report');
  finally
    oHTTP.Free;
  end;
end;

Wybierz wartość odzwierciedlającą to, co dany punkt końcowy naprawdę powinien zwracać. Jeśli wywołujesz API JSON odpowiadające kilkoma kilobajtami, limit kilku megabajtów jest hojny, a i tak zamienia odczyt bez ograniczeń w odczyt ograniczony.

Ciasteczka przypisane do całej domeny najwyższego poziomu

Odpowiedź może przysłać ciasteczko z atrybutem Domain. Nic w formacie protokołu nie powstrzymuje jej przed zgłoszeniem Domain=com czy Domain=co.uk. Klient, który to zaakceptuje, jest teraz gotów wysłać ciasteczko do każdego hosta pod tym sufiksem, co w świecie ciasteczek jest równoznaczne z rozdaniem klucza uniwersalnego. Przeglądarki odrzucają to od lat, korzystając z Public Suffix List.

RejectPublicSuffixCookies wprowadza tę samą regułę do komponentu. Ciasteczko, którego domeną jest sufiks publiczny, a nie prawdziwa domena rejestrowalna, zostaje odrzucone. Zwróć uwagę, że włączenie tej opcji uruchamia także zarządzanie ciasteczkami w kliencie, ponieważ to właśnie magazyn ciasteczek jest filtrowany.

oHTTP.RejectPublicSuffixCookies := True;

// Set-Cookie: session=abc; Domain=co.uk   ->  rejected
// Set-Cookie: session=abc; Domain=example.co.uk  ->  accepted

Wersja 2026.7 naprawia także dwa powiązane błędy dotyczące ciasteczek i przekierowań, które po cichu działały źle. Względne nagłówki Location są teraz poprawnie rozwiązywane do adresów bezwzględnych, przekierowanie 307 lub 308 zachowuje teraz metodę i treść żądania, tak jak wymaga tego specyfikacja, a ciasteczko, którego daty wygaśnięcia nie da się sparsować, jest traktowane jako ciasteczko sesyjne zamiast być odrzucane. Max-Age nadal ma pierwszeństwo przed Expires, gdy obecne są oba.

Wysyłanie dużej treści: Expect: 100-continue i żądania fragmentowane

Nie każda nowa opcja dotyczy ataków, dwie z nich chronią przed marnowaniem pasma. Jeśli wysyłasz metodą POST dużą treść do punktu końcowego, który i tak ją odrzuci (błędne poświadczenia, zły typ zawartości, wyczerpany limit), domyślne zachowanie polega na przepchnięciu najpierw całej treści przez sieć i odczytaniu kodu 401 dopiero potem.

UseExpect100Continue wysyła nagłówki żądania z Expect: 100-continue i czeka, aż serwer potwierdzi, że jest gotów przyjąć treść. Jeśli serwer odpowie od razu statusem końcowym, ładunek nigdy nie zostanie wysłany. Jeśli zgoda w ogóle nie nadejdzie, treść i tak zostanie wysłana, więc serwer ignorujący ten nagłówek nadal działa.

UseChunkedTransferEncoding obejmuje przypadek odwrotny, czyli przesyłanie, którego rozmiaru nie znasz z góry. Treść jest wysyłana we fragmentach zamiast ze stałym Content-Length, więc możesz strumieniować ją z generatora, kompresora lub potoku bez buforowania całości.

uses
  Classes, sgcHTTP_Client;

var
  oHTTP: TsgcIdHTTP;
  oBody: TStream;
begin
  oHTTP := TsgcIdHTTP.Create(nil);
  try
    oHTTP.UseExpect100Continue       := True;   // ask before uploading
    oHTTP.UseChunkedTransferEncoding := True;   // size not known up front

    oBody := TFileStream.Create('large_upload.bin', fmOpenRead or fmShareDenyWrite);
    try
      vResponse := oHTTP.Post('https://api.example.com/v1/upload', oBody);
    finally
      oBody.Free;
    end;
  finally
    oHTTP.Free;
  end;
end;

Te same przełączniki w gotowych klientach API

Ciekawe jest to, gdzie te właściwości się znajdują. Zostały ponownie udostępnione w klasie TsgcHTTPAPI_client, która jest klasą bazową każdego gotowego klienta REST w bibliotece. Oznacza to, że ustawiasz je bezpośrednio na komponencie, którego już używasz, bez sięgania po niskopoziomowy obiekt HTTP pod spodem.

Obejmuje to klientów AI i LLM (OpenAI, Anthropic, Gemini, DeepSeek, Grok, Mistral, Ollama), klienta OAuth2, Google Cloud, WhatsApp, AWS SQS i WebPush oraz moduły API Amazon AWS i Google Cloud.

uses
  sgcHTTP_API_OpenAI;

var
  oOpenAI: TsgcHTTP_API_OpenAI;
begin
  oOpenAI := TsgcHTTP_API_OpenAI.Create(nil);
  try
    oOpenAI.StripAuthOnCrossHostRedirect := True;   // never leak the API key
    oOpenAI.NoInsecureRedirect           := True;   // never fall back to http
    oOpenAI.RejectPublicSuffixCookies    := True;
    oOpenAI.MaxResponseSize              := 32 * 1024 * 1024;
    oOpenAI.MaxChunkSize                 := 1024 * 1024;

    // ... use the client as usual
  finally
    oOpenAI.Free;
  end;
end;

Klucz API wysyłany do dostawcy LLM to dokładnie ten rodzaj poświadczenia, którego nie chcesz puszczać za przekierowaniem poza domenę dostawcy, więc to właśnie tutaj włączenie tych przełączników nic nie kosztuje, a daje bardzo dużo.

HTTP/2 i HTTP/3

Klient HTTP/2 otrzymuje te same zabezpieczenia poprzez swój obiekt opcji TsgcWSHTTP2Client_Options, a klient HTTP/3 udostępnia limit odpowiedzi bezpośrednio.

uses
  sgcHTTP;

var
  oHTTP2: TsgcHTTP2Client;
begin
  oHTTP2 := TsgcHTTP2Client.Create(nil);
  try
    oHTTP2.HTTP2Options.StripAuthOnCrossHostRedirect := True;
    oHTTP2.HTTP2Options.NoInsecureRedirect           := True;
    oHTTP2.HTTP2Options.MaxResponseSize              := 10 * 1024 * 1024;
  finally
    oHTTP2.Free;
  end;
end;

// HTTP/3
oHTTP3.MaxResponseSize := 10 * 1024 * 1024;

Jedna linia dla bezpiecznej konfiguracji bazowej TLS

Nic z powyższego nie pomoże, jeśli warstwa TLS niczego nie weryfikuje. sgcWebSockets 2026.7 dodaje na to gotowe ustawienie. Każdy obiekt opcji TLS w bibliotece, zarówno w komponentach WebSocket, jak i HTTP, publikuje właściwość Preset typu TsgcTLSPreset. Domyślnie ma ona wartość tlspCustom, która zachowuje to, co skonfigurowałeś ręcznie.

Ustawienie jej na tlspSecureDefaults włącza jedną linią weryfikację certyfikatu, TLS 1.2 lub nowszy oraz weryfikację nazwy hosta.

uses
  sgcWebSocket_Types;

begin
  oHTTP.TLSOptions.Preset := tlspSecureDefaults;
  // certificate verification on, TLS 1.2+, hostname verification on
end;

Połącz to gotowe ustawienie z przełącznikami przekierowań i limitami odpowiedzi, a otrzymasz klienta, który weryfikuje, z kim rozmawia, odmawia degradacji połączenia, nie przekazuje swoich poświadczeń stronie trzeciej i nie da się go zmusić do przydzielenia nieograniczonej pamięci.

.NET

Edycja zarządzana dostarcza te same zabezpieczenia w 2026.7. Usuwanie poświadczeń przy przekierowaniach, blokada degradacji HTTPS, limity pojedynczego fragmentu i całej odpowiedzi, odrzucanie ciasteczek dla sufiksów publicznych, Expect: 100-continue oraz fragmentowane treści żądań, wszystko to jest na miejscu, łącznie z zestawem klientów HTTP/2. Te same nazwy, notacja C#.

client.StripAuthOnCrossHostRedirect = true;
client.NoInsecureRedirect           = true;
client.RejectPublicSuffixCookies    = true;
client.MaxResponseSize              = 10 * 1024 * 1024;
client.MaxChunkSize                 = 1024 * 1024;

Dostępność

Opcje wzmacniające bezpieczeństwo klienta HTTP są dostępne w sgcWebSockets 2026.7 dla Delphi od 7 do 13 oraz C++Builder (Win32/Win64, Linux64, macOS, Android i iOS), a także w edycji .NET. Wszystko, co tu opisano, jest opcjonalne i domyślnie wyłączone, więc aktualizacja przebiega bezproblemowo.

Klienci z aktywną subskrypcją mogą pobrać nową kompilację ze strefy klienta. Użytkownicy wersji próbnej mogą pobrać zaktualizowany instalator pod adresem esegece.com/products/websockets/download.

Pytania, uwagi albo pomoc w wyborze przełączników, które powinna włączyć Twoja aplikacja? Skontaktuj się z nami, odpowiedzą Ci osoby, które napisały ten kod.