Rafforzare la sicurezza del client HTTP in sgcWebSockets | Blog eSeGeCe

Rafforzare la sicurezza del client HTTP in sgcWebSockets

· Componenti

Tempo fa abbiamo scritto di come rafforzare la sicurezza del server HTTP. Questo articolo è l'altra metà della storia. Un client viene normalmente considerato la parte affidabile della conversazione, ma non lo è: invia le tue chiavi API, segue qualunque redirect restituisca l'host remoto, legge l'intera risposta in memoria e memorizza tutti i cookie che la risposta gli chiede di memorizzare. Un endpoint ostile, o semplicemente compromesso, può rivolgere contro di te tutti e quattro questi comportamenti.

sgcWebSockets 2026.7 aggiunge una serie di interruttori lato client che chiudono ognuno di questi buchi. Ognuno di essi è disattivato per impostazione predefinita, quindi l'aggiornamento non cambia nulla in un'applicazione esistente finché non decidi di attivarlo. Sono disponibili sul client HTTP di basso livello, su ogni client REST e AI già pronto, e sui client HTTP/2 e HTTP/3.

Credenziali che seguono un redirect

La classica fuga di dati lato client. Chiami un'API con un header Authorization, il server risponde con un 302 che punta a un host diverso e il client ripete diligentemente la richiesta là, header e cookie inclusi. Il tuo bearer token è appena stato consegnato a una macchina con cui non avevi alcuna intenzione di parlare. Il redirect non deve nemmeno essere malevolo, basta un attaccante in grado di sfruttare un open redirect sull'host legittimo.

StripAuthOnCrossHostRedirect rimuove gli header Authorization e Cookie non appena un redirect esce dall'host originale. La richiesta segue comunque il redirect, semplicemente arriva senza le tue credenziali.

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;

Redirect che effettuano il downgrade a HTTP in chiaro

Il secondo problema dei redirect. Parti da https://, il server risponde con un Location che inizia con http:// e il client ripete allegramente la richiesta in chiaro. Chiunque si trovi lungo il percorso può ora leggere il corpo e riscrivere la risposta. È esattamente il downgrade che HSTS esiste per impedire nei browser, e fino ad ora il client HTTP non aveva un equivalente.

NoInsecureRedirect rifiuta un redirect che sposterebbe la richiesta da HTTPS a HTTP. Il redirect viene respinto anziché seguito.

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');

I due interruttori sui redirect sono indipendenti e nella pratica li vuoi entrambi. Attiva il primo e un redirect cross-host non porterà più con sé il tuo token. Attiva il secondo e un redirect non potrà più privarti della sicurezza del trasporto.

Risposte senza limiti di dimensione

Un client HTTP predefinito legge tutto quello che il server invia. Se il server invia un corpo di risposta di dieci gigabyte, o una risposta chunked in cui un singolo chunk dichiara una lunghezza di due gigabyte, il client prova ad allocare spazio per esso. È un denial of service in una riga contro il tuo stesso processo, e non richiede che l'attaccante violi nulla, solo che risponda alla tua richiesta con più dati di quanti te ne aspettassi.

Due limiti chiudono questa falla. MaxResponseSize limita la dimensione totale del corpo di una risposta e MaxChunkSize limita un singolo chunk di una risposta chunked. Entrambi interpretano lo zero come "illimitato", che è il valore predefinito, quindi non cambia nulla finché non li imposti. Quando un limite viene superato, la lettura viene interrotta e viene sollevata un'eccezione, il client non continua a far crescere il buffer.

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;

Scegli un numero che rifletta ciò che l'endpoint dovrebbe realmente restituire. Se stai chiamando un'API JSON che risponde con pochi kilobyte, un limite di qualche megabyte è generoso e trasforma comunque una lettura illimitata in una lettura limitata.

Una risposta può inviare un cookie con un attributo Domain. Nulla nel formato di trasmissione le impedisce di rivendicare Domain=com o Domain=co.uk. Un client che lo accetta è ora disposto a inviare il cookie a ogni host sotto quel suffisso, che è l'equivalente in termini di cookie di consegnare una chiave universale. I browser lo rifiutano da anni usando la Public Suffix List.

RejectPublicSuffixCookies porta la stessa regola nel componente. Un cookie il cui dominio è un suffisso pubblico anziché un vero dominio registrabile viene scartato. Nota che abilitarlo attiva anche la gestione dei cookie sul client, perché è proprio il contenitore dei cookie ciò che filtra.

oHTTP.RejectPublicSuffixCookies := True;

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

La 2026.7 corregge anche due bug correlati su cookie e redirect che erano silenziosamente sbagliati. Gli header Location relativi vengono ora risolti correttamente in URL assoluti, un redirect 307 o 308 preserva ora il metodo e il corpo della richiesta come richiede la specifica, e un cookie la cui data di scadenza non può essere interpretata viene trattato come cookie di sessione anziché essere scartato. Max-Age continua a prevalere su Expires quando sono presenti entrambi.

Caricare un corpo di grandi dimensioni: Expect: 100-continue e richieste chunked

Non tutte le nuove opzioni riguardano gli attacchi, due di esse riguardano il non sprecare banda. Se fai POST di un corpo di grandi dimensioni verso un endpoint che sta per rifiutarlo (credenziali errate, content type sbagliato, una quota già esaurita), il comportamento predefinito è spingere prima l'intero corpo sulla rete e leggere il 401 dopo.

UseExpect100Continue invia gli header della richiesta con un Expect: 100-continue e attende che il server dichiari di essere disposto ad accettare il corpo. Se il server risponde invece con uno stato finale, non invii mai il payload. Se non arriva alcun via libera, il corpo viene inviato comunque, quindi un server che ignora l'header continua a funzionare.

UseChunkedTransferEncoding copre il caso opposto, un upload la cui dimensione non conosci in anticipo. Il corpo viene inviato in chunk anziché con un Content-Length fisso, così puoi trasmetterlo in streaming da un generatore, un compressore o una pipe senza doverlo prima bufferizzare per intero.

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;

Gli stessi interruttori sui client API già pronti

La parte interessante è dove risiedono queste proprietà. Sono riesposte su TsgcHTTPAPI_client, la classe base di ogni client REST già pronto della libreria. Questo significa che le imposti direttamente sul componente che stai già usando, senza dover ricorrere all'oggetto HTTP di basso livello sottostante.

Questo copre i client AI e LLM (OpenAI, Anthropic, Gemini, DeepSeek, Grok, Mistral, Ollama), il client OAuth2, Google Cloud, WhatsApp, AWS SQS e WebPush, e le unit API di Amazon AWS e 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;

Una chiave API inviata a un provider LLM è esattamente il tipo di credenziale che non vuoi veder seguire un redirect fuori dal dominio del provider, quindi questo è il punto in cui attivare gli interruttori non costa nulla e rende moltissimo.

HTTP/2 e HTTP/3

Il client HTTP/2 ottiene le stesse protezioni attraverso il suo oggetto delle opzioni, TsgcWSHTTP2Client_Options, e il client HTTP/3 espone direttamente il limite sulle risposte.

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;

Una riga per una configurazione TLS sicura di base

Niente di quanto sopra serve se il livello TLS non verifica nulla. sgcWebSockets 2026.7 aggiunge un preset per questo. Ogni oggetto delle opzioni TLS della libreria, sia sui componenti WebSocket sia su quelli HTTP, pubblica una proprietà Preset di tipo TsgcTLSPreset. Il valore predefinito è tlspCustom, che mantiene quello che hai configurato a mano.

Impostarla a tlspSecureDefaults attiva la verifica del certificato, TLS 1.2 o superiore e la verifica del nome host in una sola riga.

uses
  sgcWebSocket_Types;

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

Combina il preset con gli interruttori sui redirect e i limiti sulle risposte e ottieni un client che verifica con chi sta parlando, rifiuta il downgrade, non consegna le proprie credenziali a terzi e non può essere indotto ad allocare memoria senza limiti.

.NET

L'edizione gestita include le stesse protezioni nella 2026.7. La rimozione delle credenziali sui redirect, il blocco del downgrade HTTPS, i limiti per chunk e sul totale della risposta, il rifiuto dei cookie con suffisso pubblico, Expect: 100-continue e i corpi di richiesta chunked ci sono tutti, incluso il set di client HTTP/2. Stessi nomi, con la convenzione di maiuscole del C#.

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

Disponibilità

Le opzioni di hardening del client HTTP sono disponibili in sgcWebSockets 2026.7 su Delphi da 7 a 13 e C++Builder (Win32/Win64, Linux64, macOS, Android e iOS), e nell'edizione .NET. Tutto quanto descritto qui è opzionale e disattivato per impostazione predefinita, quindi l'aggiornamento è immediato.

I clienti con un abbonamento attivo possono scaricare la nuova build dall'area clienti. Gli utenti della versione di prova possono scaricare l'installer aggiornato su esegece.com/products/websockets/download.

Domande, commenti o bisogno di aiuto per decidere quali di questi interruttori la tua applicazione dovrebbe attivare? Contattaci, riceverai una risposta dalle persone che hanno scritto il codice.