Deep Dive - Cross-Site Request Forgery

Ciao!

In questo post voglio descrivere in cosa consiste un attacco famoso nel contesto delle applicazioni web noto con il nome di Cross-Site Request Forgery, spesso abbreviato in CSRF. Questo attacco è legato al modo in cui le sessioni sono gestite nelle applicazioni web tramite l’utilizzo dei cookie.

Per iniziare, consideriamo che CSRF è un attacco client-side, e con questo si intende il fatto che il “target” di questo attacco non è il server dell’applicazione, ma è un client. È dunque un attacco che mira a compromettere account di altre persone.

Nell’esempio a seguire supponiamo che l’applicazione vulnerabile sia esposta nel dominio vulnerable.it, mentre l’attaccante controlla il sito malevolo attacker.it. Come applicazione vulnerabile possiamo utilizzare l’applicazione DVWA, disponibile nel seguente link:

Potete far partire il container docker di DVWA con il seguente comando

docker run --name dvwa --rm -d -it -p127.0.0.1:80:80 vulnerables/web-dvwa

E possiamo poi mappare i due domini andando a scrivere nel file /etc/hosts il seguente contenuto

127.0.0.1 vulnerable.it attacker.it

Per poter essere attaccati, varie condizioni devono essere rispettate:

  • L’applicazione in questione non ci protegge da questa problematica.
  • Con l’applicazione vulnerabile abbiamo stabilito una sessione autenticata, gestita tramite dei cookie. In altre parole, abbiamo fatto il login, e se navighiamo con il browser sul sito vulnerable, il sito si ricorda che siamo loggati e ci fa vedere una nostra pagina privata.
  • L’applicazione vulnerabile presenta dei form che permettono di cambiare “lo stato” dell’utente, e questi form contengono parametri completamente predicibili dall’attaccante.

Ad esempio possiamo immaginare un form di cambio password. Oppure un form di cambio email. O anche, un form per l’invio di un bonifico bancario. Insomma, ogni form “in scrittura” è potenzialmente vulnerabile ad attacchi CSRF.

L’attacco vero e proprio si può effettuare in modi diversi, a seconda della situazione. In ultima analisi però, il pattern comunque è che per poter exploitare questa vulnerabilità è necessario portare la vittima a navigare su una pagina HTML malevola costruita dall’attaccante, ad esempiohttps://attacker.it/malicious.html.

A questo punto ci chiediamo:

Perché è potenzialmente pericoloso navigare su pagine HTML malevole?

La risposta a questa domanda la possiamo ritrovare in un meccanismo del browser che diamo quasi per scontato: l'autenticazione implicita.

Quando effettuiamo il login su molte applicazioni web, a valle del login, queste rilasciano un cookie. Il cookie è (o almeno, dovrebbe essere) formato da una sequenza di caratteri randomici, il cui obiettivo è agire come identificativo. Il cookie identifica un particolare client. In altre parole, il cookie è usato come segnale di autenticazione. Quando il server vede il cookie di sessione, capisce chi siamo, e tutte le azioni effettuate inviando il cookie di sessione saranno effettuate sull’account associato a tale cookie.

Riporto a seguire la richiesta HTTP di autenticazione ripresa da DVWA. Ho solo tolto qualche header poco interessante.

POST /login.php HTTP/1.1
Host: localhost
Content-Length: 85
Content-Type: application/x-www-form-urlencoded

username=admin&password=admin&Login=Login&user_token=32bdbe594a5d83a894c984f409c036cd

Segue anche la risposta HTTP. Notiamo che in tale risposta è presente il cookie di sessione. Quando il browser leggere l’header Set-Cookie, imposta tale cookie all’interno della propria “cookie jar”, un piccolo database di cookie.

HTTP/1.1 302 Found
Date: Sun, 09 Feb 2025 12:40:23 GMT
Server: Apache/2.4.25 (Debian)
Set-Cookie: PHPSESSID=om2339fnlfe51rqdhcbobcmmq7; path=/
Set-Cookie: security=low
Location: login.php

Il cookie in questo caso si chiama PHPSESSID, il tipico cookie generato dall’ambiente php, ed il suo valore è om2339fnlfe51rqdhcbobcmmq7.

Una volta che abbiamo il cookie, questo viene inviato dal browser in modo implicito nelle successive richieste al sito. In altre parole, ad ogni richiesta HTTP effettuata dal codice HTML dell’applicazione web, il browser ci “attacca” il cookie. Le richieste in cui il cookie viene “attaccato” in automatico sono richieste dette implicitamente autenticate.

  • Esempio richiesta autenticata
POST /changePassword.php HTTP/1.1
Host: localhost
Content-Length: 85
Content-Type: application/x-www-form-urlencoded
Cookie: PHPSESSID=om2339fnlfe51rqdhcbobcmmq7;

password=...
  • Esempio richiesta non autenticata
POST /changePassword.php HTTP/1.1
Host: localhost
Content-Length: 85
Content-Type: application/x-www-form-urlencoded

password=...

È importante osservare che non è possibile avere un diretto controllo su questo meccanismo implicito di autenticazione, in quanto la logica di gestione di queste cose sono contenute all’interno della logica del browser. Da utenti “esterni” abbiamo un controllo limitato.

Da notare ovviamente che questo meccanismo di autenticazione implicita è, da una parte molto utile a livello di usability, ma è pericoloso nel contesto della security. Cosa succede se un attaccante riesce a forzare il nostro browser ad inviare una richiesta con il cookie di sessione? Sarebbe in grado di eseguire determinate azioni sul nostro profilo utente.

Per cercare di avere più controllo su questi meccanismi di autenticazione implicita e nello specifico per aumentare la sicurezza del meccanismo, nel corso degli anni sono stati introdotti vari “attributi” che vanno a modificare leggermente questo comportamento, per cercare di renderlo più sicuro. Tra questi troviamo i seguenti

  • HTTPOnly
  • Secure
  • SameSite

Un attributo chiave per difendere le applicazioni da attacchi di tipo CSRF è proprio il SameSite. Tale attributo permette di controllare meglio il modo in cui il browser gestisce l’autenticazione implicita nelle richieste cross-site, ovvero nelle richieste che partono da un sito, ad esempio attacker.it, e che arrivano ad un altro sito, ad esempio vulnerable.it. Per adesso il focus non è capire il funzionamento del cookie SameSite, ma è utile specificare che può assumere tre valori diversi:None, Lax e Strict, che danno valori di sicurezza crescenti da sinistra vers destra (None non garantisce nessuna sicurezza aggiuntiva, mentre Srtict garantisce la sicurezza massima).

Con queste informazioni, possiamo quindi rispondere dalla domanda che ci eravamo posti: è potenzialmente pericoloso navigare su pagine HTML perché una pagina HTML malevola può forzare il browser ad effettuare una richiesta implicitamente autenticata verso una applicazione vulnerabile a CSRF. Questo potrebbe forzare il browser ad effettuare una azione non voluta dalla vittima nel contesto del sito vulnerable.

Cerchiamo quindi di capire come implementare questa tipologia di attacchi.

Come fa un attaccante a forzare il browser ad effettuare questa richiesta malevola?

Qui è importante considerare le connessioni che esistono tra l’HTML di una pagina web e il traffico HTTP generato da un browser che processa tale pagina HTML.

Consideriamo ad esempio un semplice tag img. Questi tag si trovano in tantissime pagine sul web, e vengono usati per specificare delle risorse di tipo immagine.

<img src="dvwa/images/logo.png" alt="Damn Vulnerable Web Application" />

Notiamo che tale elemento HTML, quando viene letto e processato dal browser, andrà a generare una specifica richiesta HTTP, il cui obiettivo è proprio andare a scaricare la risorse immagine dallURL specificato nell’elemento. Seguente la richiesta HTTP associata al tag mostrato

GET /dvwa/images/login_logo.png HTTP/1.1
Host: localhost
sec-ch-ua-platform: "Linux"
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36
sec-ch-ua: "Chromium";v="131", "Not_A Brand";v="24"
sec-ch-ua-mobile: ?0
Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: no-cors
Sec-Fetch-Dest: image
Referer: http://localhost/login.php
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie: PHPSESSID=s77oi1js1lu9sk3onnpcejmr66; security=low
Connection: keep-alive

Questo piccolo esempio mette in evidenza il fatto che lHTML è strettamente legato al protocollo HTTP, e che molti elementi HTML sono “codifiche implicite” di richieste HTTP, nel senso che quando vengono letti e processati dal browser, quest’ultimo genera del traffico HTTP.

Un altro tag utilizzato molto spesso per codificare richieste HTTP è il tag <form>. Ogni form HTML altro non è che una codifica implicita di una richiesta HTTP. Avete presente la richiesta HTTP che ho fatto vedere prima, quella relativa al login? Quella richiesta è implicitamente codificata nel seguente form HTML

<form action="login.php" method="post">
	<fieldset>
		<label for="user">Username</label> <input type="text" class="loginInput" size="20" name="username"><br />
		<label for="pass">Password</label> <input type="password" class="loginInput" AUTOCOMPLETE="off" size="20" name="password"><br />
		<br />
		<p class="submit"><input type="submit" value="Login" name="Login"></p>
	</fieldset>
	<input type='hidden' name='user_token' value='57cdb6c19d39be95b1aac02c6d2b4744' />
	</form>

Notiamo infatti che ogni input field del form si trasforma in un parametro presente nella richiesta HTTP. Notiamo inoltre il parametero user_token. Questo parametro, che ha un type=hidden per non essere mostrato all’utente, è un altro meccanismo di protezione che può essere usato per difendersi da attacchi di tipo CSRF (può essere combinato con l’attributo SameSite).

L’idea quindi è che un attaccante può codificare un form malevolo che va ad effettuare una specifica richiesta al sito vulnerable vulnerable.it. Quando un utente naviga sulla pagina malevola, il browser leggerà il form, e tramite una singola riga di codice JavaScript, è possibile effettuare il submit del form in automatico. Quando il browser effettua il submit di un form, il meccanismo di autenticazione implicita viene triggerato, e dunque il browser andrà ad “attaccare” il cookie di autenticazione della vittima all’interno della richiesta.

In altre parole, l’attaccante può essere in grado di forzare la vittima ad effettuare richieste autenticate verso un sito vulnerabile.

Un Esempio Pratico

Per fare capire meglio la vulnerabilità, consideriamo un esempio pratico ripreso da livello low di DVWA. Nello specifico, consideriamo il seguente form, che implementa un tipico cambio password. Graficamente il form viene visualizzato nel seguente modo

Notiamo che presenta due campi, quello per la nuova password, e quello per confermare la nuova password. Secondo voi è vulnerabile?

Datevi un po’ di tempo per provare a rispondere, prima di continuare a leggere.

È vulnerabile?

Sì, tale form è potenzialmente vulnerabile ad un attacco di tipo CSRF.

Andiamo adesso a vedere il codice HTML che codifica tale form.

<form action="#" method="GET">
	New password:<br />
	<input type="password" AUTOCOMPLETE="off" name="password_new"><br />
	Confirm new password:<br />
	<input type="password" AUTOCOMPLETE="off" name="password_conf"><br />
	<br />
	<input type="submit" value="Change" name="Change">
</form>

Tale form contiene tre input: il campo password_new, il campo password_conf, ed il campo Change. Se effettuiamo il submit con il valore “password”, il browser genera la seguente richiesta HTTP.

GET /vulnerabilities/csrf/?password_new=password&password_conf=password&Change=Change HTTP/1.1
Host: localhost
Referer: http://localhost/vulnerabilities/csrf/
Cookie: PHPSESSID=jdbmr69jort1gb4c0joev437i6; security=low;

Notiamo che in tale richiesta i parametri sono inseriti all’interno di una richiesta GET, e notiamo inoltre che il cookie di sessione sia stato inserito dal browser in modo implicito.

Il form è vulnerabile a CSRF in quanto non richiedere di inviare il valore della password attuale. In altre parole, manca un campo, il campo current password, che deve essere utilizzato per autenticare correttamente l’utente.

Cosa può fare un attaccante in questo caso? L’attacante procedere andando a creare la seguente pagina HTML malevola, salvata nel file malicious.html.

<html>
  <head></head>
  <body>
    <form action="http://localhost/vulnerabilities/csrf/" method="GET">
      <input type="hidden" name="password_new" value="attacker">
      <input type="hidden" name="password_conf" value="attacker">
      <input type="hidden" name="Change" value="Change">
    </form>

    <script>
      document.forms[0].submit()
    </script>
  </body>
</html>

Notiamo che tale pagina è costruita in modo apposito per generare la richiesta malevola voluta dall’attaccante. In questa richiesta l’attaccante sta codificando le sue intenzioni malevole, che saranno poi eseguite dal browser della vittima.

  • Nell’attributo action del form mettiamo l’URL completo che punta alla funzionalità vulnerabile del sito target, in questo caso è http://localhost/vulnerabilities/csrf/.
  • Tutti i campi sono hidden e hanno dei valori prefessiati tramite l’attributo value. Questi valori sono scelti dell’attaccante.
  • Quella singola riga di JavaScript viene usata per andare ad effettuare il submit del form non appena la pagina viene caricata.

È possibile esporre questa pagina in un server locale con python. Possiamo ad esempio usare il seguente comando

python3 -m http.server 1337

Così facendo abbiamo che in localhost c’è la nostra app vulnerabile, mentre in localhost:1337 c’é il sito dell’attaccante. Se adesso apriamo il browser con il quale siamo loggati in DVWA, e navighiamo nella pagina dell’attaccante http://localhost:1337/malicious.html, il nostro browser farà le seguenti cose:

  • Legge l’HTML malevolo, malicious.html.
  • L’HTML gli dice di prepare il form e di inviarlo.
  • Il browser invia il form al sito vulnerabile.
  • Dato che l’utente è già autenticato con tale sito, il browser, tramite il meccanismo della autenticazione implicita, attacca il cookie dell’utente alla richiesta.
  • Il sito vulnerabile riceve una richiesta di cambio password, autenticata con il cookie della vittima. La richiesta contiene tutti i campi necessari per poter essere processata, e quindi viene processata sembra problemi.
  • Il processamento della richiesta porta l’utente vittima a cambiare la propria password.

La vulnerabilità è adesso chiara: con questo flusso di eventi, l’attaccante è stato in grado di cambiare la password della vittima all’interno del sito vulnerabile. E l’unica azione effettuata dalla vittima è stata quella di navigare sulla pagina malevola con lo stesso browser con cui era autenticato al sito vulnerabile.

Cosa fare per proteggersi?

Ci sono due punti di vista diversi qui. Quello dell’applicazione, ovvero cosa può fare chi scrive il software applicativo per difendersi dalla vulnerabilità, e quello dell’utente finale. Partiamo dal secondo, in quanto è più semplice.

Una protezione, semplice da effettuare, ma molto utile, è quella di utilizzare sessioni anonime per aprire pagine potenzialmente pericolose. Non sempre è possibile farlo, in quanto certe volte il browser entra in catene di redirect che alla fine portano in pagine malevole. Ma se vediamo un link potenzialmente pericoloso, l’idea è quella di aprirlo con un browser anonimo. L’utilità del browser anonimo è che non è autenticato con nessuna applicazione. In altre parole, il browser anonimo non ha cookie disponibili, e quindi si evita per costruzione il problema dellautenticazione implicita. Se non ho cookie, non posso “attaccarli” a nessuna richiesta: sia quelle legittime che quelle malevole.

Rispetto invece a chi sviluppa il software, nel corso degli anni sono stati sviluppati vari meccanismi di protezione. Sono due nello specifico quelli usati in generale.

Il primo è l’attributo SameSite. Senza entrare in dettaglio, per farla breve, questo attributo cambia il modo in cui il browser gesiste il meccanismo di autenticazione implicita. Nello specifico, impedisce che il cookie venga inserito di default in determinate richieste classificate come cross-site. Supponiamo che la richiesta malevola parte dal sito malevolo attacker.it e va al sito vittima vulnerable.it. Questo tipo di richiesta è detta cross-site, perché i domini di partenza e quello di arrivo differiscono nel valore del sottodominio principale.

Il secondo meccanismo di protezione è tramite l’utilizzo di un token speciale, detto token-anti-CSRF. Il valore del token è un valore randomico, che deve essere legato alla sessione utente. Questo token viene inserito come input type="hidden" ad ogni form presente nell’applicazione. La presenza di questo token garantisce che anche nel caso in cui l’attaccante riesce a forzare la vittima ad eseguire una richiesta con il cookie di autenticazione, comunque la richiesta non sarà valida, perché priva di questo specifico token. È compito del server validare la presenza e il valore del token.

Il caso del cambio password è un caso speciale, in cui è possibile inserire il valore old_password, per forzare l’autenticazione da parte del cliente, anche se ha già un cookie di sessione autenticato. Se aggiungiamo questo campo, l’attaccante non è più in grado di preparare una pagina malevola funzionante, in quanto tale pagina dovrà contenere anche il valore corretto di old_password, e l’attaccante non conosce l’attuale password della vittima, altrimenti non avrebbe bisogno di attaccarla.

Ci sarebbe molto altro da dire, ma volevo fare una piccola “deep dive” sulla questione. Se avete domande o feedback, condividete pure. In futuro ci farò un video sopra leggendo proprio quanto scritto, quindi eventuali domande potranno essere risposte direttamente nel video.

Grazie per aver letto.

1 Like