Creare siti in PHP – (29) – La sicurezza in PHP: session hijacking

Terminiamo oggi la breve panoramica sui più comuni problemi di sicurezza con una puntata sul session hijacking, che consiste nel “furto dell’identità” di un utente che si collega a un sito web.
Session hijacking: che cos’è
Che cosa fa il malintenzionato che sottopone l’utente di un sito a session hijacking? Non fa altro che rubare l’identificativo di sessione dell’utente (ogni sessione ha un proprio id) per poi loggarsi al sito spacciandosi per l’utente a cui ha rubato le credenziali. Uno dei metodi più comuni per rubare le credenziali è sfruttare il cross-site scripting: abbiamo però già parlato nell’episodio precedente di come difenderci da questo tipo di attacchi, quindi vedremo oggi come coprirci anche da un furto ottenuto tramite “session fixation”: con la session fixation, i dati verranno rubati tramite una sessione creata ad hoc dal malintenzionato.
Certo, affinché si verifichi una session fixation devono essere soddisfatte due condizioni: codice vulnerabile e utente sprovveduto. Nella puntata sulle sessioni non abbiamo parlato del modo in cui l’id di sessione viene propagato: ciò può avvenire tramite un cookie che viene appositamente creato per contenere l’id (cookie che si estinguerà alla chiusura del browser) oppure, nel caso in cui i cookie siano stati disabilitati, tramite metodi get e post. Il ladro di sessioni può convincere l’utente del nostro sito (magari tramite una mail o con un post su un blog) a collegarsi a un indirizzo simile a questo: www.nostrosito.it/login.php?PHPSESSID=12345 dove, attraverso la query string e ipotizzando che l’id di sessione venga propagato tramite get, decide il numero di sessione.
Simuliamo un attacco
Per comprendere meglio il meccanismo di un attacco session fixation proviamo a simularne uno (ma, come detto nella scorsa puntata, solo affinché vi possiate difendere). Per prima cosa assicuriamoci che nelle impostazioni del nostro PHP sia abilitata la trasmissione dei dati di sessione attraverso url: clicchiamo quindi sull’icona di Wamp, andiamo sotto la voce PHP e quindi apriamo il file php.ini. Cerchiamo adesso la direttiva session.use_trans_sid, che è quella che consente la tramissione degli id di sessione via url (a proposito: i commenti sopra di essa vi diranno che abilitarla sottopone a rischi di sicurezza). Se è a 1 lasciamola com’è, altrimenti se è impostata a 0 dobbiamo settarla a 1. Cerchiamo poi la direttiva session.use_only_cookies (permette la trasmissione degli id di sessione solo tramite cookie) e impostiamola a 0.
A questo punto creiamo un semplice codice PHP che al nostro collegamento dà inizio a una sessione che memorizza il numero di volte in cui visualizziamo la pagina con il nostro browser:
<?php
session_start();
if (!isset($_SESSION['conto'])) {
$_SESSION['conto'] = 0;
} else {
$_SESSION['conto']++;
}
echo "Numero:" . $_SESSION['conto']."";
?>
Tutto molto semplice. Con la solita funzione isset controlliamo che la variabile $_SESSION['conto'] non sia stata inizializzata: se non esiste, la creiamo e la impostiamo a 0, se invece esiste già la incrementiamo. Terminato il blocco if stampiamo la nostra variabile.
Salvate il file chiamandolo, per esempio, “fixation.php” e inseritelo in una cartella “prova” all’interno della cartella “www” di Wamp. A questo punto aprite il vostro browser preferito (per esempio Firefox), eliminate tutti i cookie di localhost e collegatevi a questo indirizzo: http://localhost/prova/fixation.php?PHPSESSID=12345. Aggiornate la pagina un qualsiasi numero di volte, arriviamo per esempio a 9.
A questo punto chiudete pure il browser. Riaprite un altro browser (esempio, Chrome), magari anche su un altro computer se fate la prova su un server remoto, e provate a collegarvi allo stesso indirizzo: come potrete notare il conto non ripartirà da 0 ma partirà da dove siete rimasti prima (nel nostro caso vedremo “10”). Cosa significa questo? Abbiamo simulato un ipotetico utente A che utilizza Firefox, apre la pagina con il link che gli è stato suggerito dal malintenzionato utente B e dà inizio a una sessione. L’utente B aprirà poi il suo Chrome con l’id di sessione da lui stesso creato e potrà accedere ai dati dell’utente A. In questo caso si tratta semplicemente del numero di volte che ha aggiornato la pagina, ma questo tipo di attacco funziona tranquillamente anche su script molto più complessi (per esempio un login a un sito web: il malintenzionato B potrà accedere indisturbato a tutti i dati personali dell’utente A) che però hanno seri problemi di sicurezza.
Come difendersi: session_regenerate_id
Come difendersi da questo attacco? Il metodo più semplice è rigenerare spesso l’id di sessione, in modo da rendere inefficaci gli attacchi: questo si può fare agevolmente tramite l’apposita funzione session_regenerate_id, che si applica in modo molto semplice:
session_regenerate_id();
E in genere va fatto dopo aver dato il via alla sessione con session_start. La documentazione ufficiale di questa funzione si trova all’indirizzo http://php.net/manual/en/function.session-regenerate-id.php. Inoltre sarebbe bene che le direttive di cui sopra, session.use_trans_sid e session.use_only_cookies, fossero impostate rispettivamente a 0 e 1. Comunque l’utilizzo di session_regenerate_id è di per sé già sufficiente per difendersi. È quindi bene rigenerare spesso, anche prima che l’utente abbia fatto login (inserendo quindi la funzione nella pagina di login, per esempio).
Come difendersi: controllo sullo user agent
Infine potremmo eseguire un controllo aggiuntivo sullo user agent dell’utente (perché niente impedisce che uno stesso id di sessione, come abbiamo visto, possa essere utilizzato da due utenti diversi): questo consente anche di avere una difesa contro attacchi di tipo “session sidejacking”. Attraverso questo tipo di attacco, comunque difficile da mettere in atto, il malintenzionato cerca di intercettare l’id di sessione attraverso i pacchetti di rete che vengono scambiati tra il server e il browser dell’utente. Per ovviare a questa minaccia può essere utile fare in modo che l’accesso venga impedito nel caso in cui lo user agent di chi richiede l’accesso al nostro sito non corrisponda a quello dell’utente che ha effettuato il login.
Per fare questo abbiamo bisogno della variabile superglobale $_SESSION: dovremo associare a essa il valore dell’elemento HTTP_USER_AGENT della variabile superglobale $_SERVER, meglio se codificato tramite funzione md5. Il tutto nella pagina o nella funzione che consente il login:
$_SESSION['HTTP_USER_AGENT'] = md5($_SERVER['HTTP_USER_AGENT']);
In questo modo abbiamo creato un elemento della sessione chiamato HTTP_USER_AGENT a cui abbiamo associato nient’altro che il nostro user agent criptato con md5. A questo punto, per eseguire il controllo, potremmo utilizzare un codice simile a questo:
if (isset($_SESSION['HTTP_USER_AGENT'])) {
if ($_SESSION['HTTP_USER_AGENT'] != md5($_SERVER['HTTP_USER_AGENT'])) {
logout();
}
}
Con questo script non facciamo altro che controllare, sempre tramite la funzione isset, se esiste la sessione con elemento “HTTP_USER_AGENT”: in caso affermativo, se il suo valore è diverso dallo user agent, sempre criptato con md5, dell’utente che sta eseguendo lo script (in altre parole se chi si è loggato ha uno user agent diverso rispetto a quello dell’utente che sta cercando di forzare l’accesso al sito), rimandiamo l’utente a una ipotetica funzione logout che distruggerà la sessione.
Anche per oggi la puntata è arrivata alla fine: vi do appuntamento alla prossima, la trentesima nonché ultima della nostra guida!
Puoi trovare interessante anche:
- Creare siti in PHP – (27) – Le sessioni e i cookie
- Creare siti in PHP – (28) – La sicurezza in PHP: XSS e SQL Injection
- Creare siti in PHP – (22) – Costruiamo il database
- Creare siti in PHP – (12) – Cenni sulle funzioni
- Creare siti in PHP – (19) – Alcune funzioni da non dimenticare! Parte 3: gestire file di testo esterni
Esprimi un giudizio sul post!
6 commenti
-
-
24 gennaio 2012 alle 20:04
Credo che il miglior sistema siano i Fraemwork. Hanno delle funzioni speciali appositamente create per la sicurezza.
-
30 gennaio 2012 alle 11:11
[...] siti in PHP – La sicurezza in PHP: session hijacking (Gabriele [...]
-
5 febbraio 2012 alle 10:58
Un paio di considerazioni se me lo permettete, usare l’identificativo di stringa è pericoloso, se il man in the middle riesce a “sniffare” la nostra sessione nulla gli impedisce di leggere HTTP_USER_AGENT e replicarlo, cosa abbastanza banale.
Normalmente l’attacco alla sessione lo si fa dopo che l’utente attaccato si è loggato, altrimenti a che servirebbe?
Allora è meglio usare un token creato ad hoc, per esempio $token = sha1(uniqid(rand(), true)); genera un token sufficientemente univoco da poter essere associato ad una sessione al posto di $_SERVER['HTTP_USER_AGENT'].
Non mettere mai la sessione di login (il form con account e password da inserire) in tutte le pagine, ma in una pagina separata, fatta ad hoc, inserire tra i campi un campo hidden con il token inserito nel value.
Al momento di generare il token, generare anche un elemento di sessione con il timestamp.
Quando l’utente effettua il login controllare che il token generato e quello inserito nel campo hidden siano uguali e che il tempo passato non sia ad esempio superiore a tre/quattro minuti.
Se accade mandare un messaggio di avviso in cui per loggarsi bisogna riportarsi sulla pagina di login e tutto ricomincia.
Tre minuti sono sufficienti per effettuare un login, e in quei tre minuti c’è una buona possibilità di non essere sotto attacco, anche se murphy….
M.PS è possibile chiedere a Laura o a chi gestisce il sito di aumentare il contrasto nei post e soprattutto negli snipset, non si legge nulla. grazie
-
5 febbraio 2012 alle 11:12
PPS

Scusa Federico, ma nell’articolo che hai letto, alla fine si parla anche di uno shifleft, ovvero di un token da attaccare alla stringa generata dal browser, e che non è possibile clonare perché generato dal server e che risiede solo nel server.
In pratica il tuo script diverrebbe:
$stringa = $_SERVER['HTTP_USER_AGENT'];
$stringa .= ‘FRASEACASO’;
$_SESSION['fingerprint'] = md5($stringa);Poi il riconoscimento avverrebbe così:
if (isset($_SESSION['fingerprint'])) {
$stringa = $_SERVER['HTTP_USER_AGENT'] . ‘FRASEACASO’;
if ($_SESSION['fingerprint'] != md5($stringa)) {
regenerate_id(true); // equivale a distruggere la sessione prima di rigenerarla
$_SESSION = array(); // distrugge tutti gli eventuali array creati dentro $_SESSION
}
}
Ovviamente questo non cambia le cose, se l’attaccante riscrive la stringa di identificazione del browser, aggiungere un parametro non ne aumenta la sicurezza, però la rende univoca. -
5 febbraio 2012 alle 22:40
@Marco
quale articolo dovrei aver letto? Il codice anti-session hijacking è tratto da “PHP Login Script”, uno script molto interessante per implementare un sistema di login all’interno del proprio sito PHP e che consiglio a tutti: ritengo sia un buon strumento sia per chi è alle prime armi sia per chi ne sa anche qualcosa in più. Evidentemente anche la presunta fonte che citi tu può essersi ispirata a questo noto script. Però in effetti hai proposto una ulteriore buona soluzione che sicuramente integra quanto scritto nell’articolo!
Per quanto riguarda il commento precedente fatico a capirne il senso: “se il man in the middle riesce a “sniffare” la nostra sessione nulla gli impedisce di leggere HTTP_USER_AGENT e replicarlo”. Beh… se è già riuscito a sniffare la nostra sessione in un altro modo per quale motivo dovrebbe replicare HTTP_USER_AGENT se conosce già i dati della sessione?
Ovviamente ribadisco che tutto quanto espresso nell’articolo NON garantisce una soluzione efficace al 100% a tutti i problemi, anche perché la sicurezza in PHP è un tema talmente complesso che avrebbe bisogno di una guida a sé, pertanto non è possibile trattarlo per intero in una guida di base.











Web magazine è il blog personale di
I contenuti di questo blog sono pubblicati sotto licenza
carlo bartolomeo scrive:
23 gennaio 2012 alle 12:15
Ciao Federico….
Puoi dare un’occhiata alla lezione 16 ‘Controlliamo un form’, al commento che riporto…
Grazie per l’attenzione.
Carlo
(sono piuttosto indietro… sigh)