Home / Indice sezione
 www.icosaedro.it 

 Sicurezza HTTP

In questo articolo si considerano alcuni aspetti di sicurezza coinvolti nel dialogo tra client e server basato sul protocollo HTTP, il popolare protocollo Internet del WEB. In particolare si analizza il problema della conservazione dello stato dell'applicazione che genera le pagine WEB tra l'invocazione di una pagina e quella successiva. Viene inoltre considerato il problema di come vincolare il client a seguire un percorso prestabilito tra le pagine del sito, qualora l'ordine di visitazione delle pagine sia significativo ai fini della corretta elaborazione delle informazioni. Viene proposto un meccanismo generale per risolvere questi problemi denominato programmazione WEB funzionale, suscettibile di essere implementato in vari modi e su diverse piattaforme di sviluppo.

L'articolo è destinato ai programmatori WEB che realizzano siti dinamici articolati, come applicazioni di e-commerce, applicazioni di gestione documentale, applicazioni intranet, soluzioni ASP, ecc.

Storico degli aggiornamenti

2003-10-25
Prima edizione dell'articolo.

Indice

Analisi del protocollo HTTP
Sessioni
Il problema della validazione delle richieste
Un esempio di applicazione WEB
Programmazione WEB funzionale
Il Registro di stato WEB
Lo stack WEB
Conclusioni
Bibliografia
Argomenti correlati

Analisi del protocollo HTTP

Nel caso più frequente, il server invia al client un documento di testo in formato HTML, anche noto come pagina WEB. Ai fini della nostra analisi, la pagina WEB contiene tre elementi:

La comunicazione dal client verso il server si attiva quando l'utente aziona un'ancora o un bottone. In entrambi i casi il client invia al server la richiesta della prossima pagina. Le informazioni contenute in questa richiesta sono di due tipi:

Di norma il percorso delle risorse viene predisposto dal server nella pagina WEB stessa ed inserito in un'ancora <A> o in un <FORM>. Anche i parametri sono spesso già inclusi nella pagina WEB sotto forma di parametri delle ancore o di valori di tipo HIDDEN nei campi del form. Altri parametri ancora vengono invece inseriti dall'utente che interagisce con la pagina WEB compilando campi di inserimento, selezionando voci da menu, ecc. Comunque sia, in ultima analisi la richiesta che il client invia al server si compone semplicemente di un pathfile e di una serie di parametri.

La componente attiva del server individuata da questo pathfile è un programma che acquisisce i parametri passati dal client ed esegue una qualche elaborazione. Il risultato ultimo di questa elaborazione è una risposta che il server ritornerà al client, spesso sotto forma di una nuova pagina WEB.

Sessioni

La natura stateless del protocollo HTTP pone il problema della conservazione dello stato del programma che genera le pagine WEB tra una richiesta di pagina e quella successiva. Il problema è stato risolto con l'aggiunta di un parametro che individua la sessione dell'utente. Una sessione è l'insieme dei valori che il programma lato server desidera associare ad uno specifico client. Queste informazioni di stato del server vengono conservate sul server e contrassegnate con una stringa generata in modo casuale e difficilmente indovinabile denominata chiave di sessione. La chiave di sessione viene inviata al client ad ogni richiesta, e può venire inserita come parametro di un URL, oppure come campo HIDDEN di un FORM, o ancora come cookie.

Di conseguenza, il client ritornerà la chiave di sessione al server ad ogni richiesta, e questa chiave permetterà al server di associare alla richiesta pervenuta il corretto insieme di informazioni di stato contenute nella sessione.

Molti sistemi di sviluppo per applicazioni WEB forniscono meccanismi che aiutano la gestione delle sessioni e dei parametri di sessione. Ad esempio, certi sistemi dispongono di generatori di chiavi di sessione, di sistemi per gestire l'autenticazione dell'utente e di utilità per salvare e poi recuperare i parametri di sessione tra una chiamata e quella successiva.

In definitiva, il browser WEB diventa l'interfaccia grafica verso una applicazione remota attraverso la quale l'utente aziona bottoni, seleziona ancore, ed inserisce dati. Le azioni dell'utente e i dati da esso inseriti vengono inviati al server che li elabora opportunamente, in base allo stato della sessione. Si tratta di un modello di interazione del tutto analogo a quello di un normale programma stand-alone: il browser presenta l'interfaccia grafica, il server memorizza lo stato di esecuzione dell'applicazione ed evade le richieste del client.

Il problema della validazione delle richieste

Il WEB è nato per consentire di accedere alla documentazione diffusa nel mondo: in qualsiasi momento il client può richiedere una qualsiasi risorsa a un qualsiasi server specificando un opportuno URL. Ma c'è di più: il client può inviare al server, e in qualsiasi momento, una combinazione qualsiasi di parametri che potrebbero anche non essere previsti dalla logica del programma. Ciò può avvenire per vari motivi:

Comunque sia, il server deve sempre essere in grado di controllare la correttezza delle richieste e prevenire elaborazioni illecite quando i parametri indicati non sono validi, o quando l'ordine di esecuzione delle operazioni seguito dal client viola il percorso previsto dalla logica del programma. Ci chiediamo ora se un tale obiettivo di programmazione sicura sia raggiungibile in modo pratico e sistematico.

Posto in questi termini, il problema non è di facile soluzione. Un sito WEB mediamente complesso può essere costituito da decine o da centinaia di pagine, ciascuna potenzialmente in grado di ricevere una richiesta arbitraria e ciascuna, quindi, obbligata ad eseguire una complessa e delicata operazione di verifica della validità della richiesta ad essa pervenuta.

E' possibile risolvere tutti questi problemi in modo diretto, e cioè facendo precedere ogni elaborazione da una verifica della sua liceità. Pertanto ad ogni richiesta pervenuta dovremo controllare se i parametri della richiesta stessa sono validi e se l'ordine di esecuzione delle operazioni rispetta i vincoli imposti dalla particolare logica del programma. E' facile capire che una tale soluzione diventa molto onerosa anche per applicazioni modeste. Per meglio comprendere il problema, prenderemo ad esempio una applicazione di home banking. Ne vedremo prima l'implementazione banale, e poi esamineremo una implementazione alternativa basata sul concetto della programmazione WEB funzionale che è l'oggetto di questo articolo.

NOTA BENE: Il concetto di programmazione WEB funzionale qui presentato non ha nulla a che vedere con la programmazione funzionale comunemente intesa. Nel nostro caso intendiamo semplicemente stabilire una analogia tra un tradizionale linguaggio di programmazione organizzato per funzioni, e una applicazione WEB dove ogni pagina è generata da una funzione o un sottoprogramma che deve essere richiamato con determinati parametri. Il fatto che queste funzioni siano in realtà implementate come altrettante pagine WEB attive o piuttosto come un unico programma è un dettaglio implementativo che non interessa la nostra discussione.

Un esempio di applicazione WEB

Consideriamo una una versione molto semplificata di una applicazione di WEB banking che dia la possibilità ai correntisti di visionare il saldo dei proprio conti correnti. L'applicazione è veramente minimale, e consiste in una pagina di ingresso che denomineremo menu() e in una pagina che visualizza il saldo di un conto che denomineremo saldo(). Questo esempio è stato scelto per le evidenti implicazioni di sicurezza e di riservatezza che comporta.

Ovviamente ogni correntista disporrà di un proprio nome utente e di una propria password di accesso segreta che permettono al sistema di identificarlo con certezza. Una volta eseguito il login, il sistema assegna una sessione all'utente e gli presenta la pagina di ingresso menu(). Dobbiamo immaginare che ogni correntista potrebbe possedere più conti correnti, e pertanto la pagina di ingresso dovrebbe proporre l'elenco dei conti correnti accessibili dall'utente (v. figura 1, a sinistra).


Figura 1. Rappresentazione schematica della applicazione di home banking proposta nel testo. Le funzioni menu() e saldo() generano le pagine HTML rappresentate dai rettangoli. Le frecce indicano l'effetto prodotto quando dal client si azionano l'ancora 123 (linea continua) o il bottone OK di destra (linea tratteggiata).


I codici dei conti correnti saranno ancore cliccabili che portano alla pagina che visualizza l'estratto conto corrispondente. Queste ancore appariranno nel codice HTML simili a queste:

<a href="saldo?cc=123">123</a>
<a href="saldo?cc=456">456</a>
<a href="saldo?cc=789">789</a>

Ogni ancora produce la chiamata ad una apposita pagina attiva del server che qui abbiamo indicato come "saldo", che in definita causa la chiamata della nostra funzione saldo() (v. figura 1 a destra). In realtà ogni sistema di sviluppo WEB ha le sue convenzioni per quanto riguarda i nomi e le estensioni delle pagine attive, ma questi dettagli non interessano il nostro discorso e quindi li omettiamo. L'URL specifica anche un parametro "cc" che contiene il codice del conto corrente da visualizzare. Ad esempio, cliccando sul conto 123 verrà invocata la pagina attiva "saldo" con parametro "cc=123". Indicheremo la stessa chiamata alla pagina attiva usando la notazione funzionale saldo(cc=128). Questa pagina attiva andrà a recuperare dalla memoria del server i dati relativi al conto richiesto e li presenterà all'utente in modo appropriato.

Diventa subito evidente anche all'utente meno esperto, che basta richiamare la pagina del saldo specificando un diverso codice di conto corrente per visualizzare dati relativi a conti correnti arbitrari. Allo scopo basta ad esempio comporre questo URL sulla barra degli indirizzi del browser:

https://un.certo.dominio.it/saldo?cc=XXX

dove XXX è il codice del conto corrente voluto. Per rimediare a questo problema dobbiamo inserire nella pagina attiva "saldo" una serie di controlli che accertino che il codice del conto appartenga effettivamente all'utente. Ricordiamoci, però, che questa verifica l'avevamo già fatta nella pagina di ingresso quando abbiamo presentato l'elenco dei conti disponibili. Stiamo pertanto ripetendo una determinazione che avevamo già stabilito.

La pagina che presenta il saldo di un dato conto si presta ad essere riutilizzata per altre funzionalità. Ad esempio, possiamo immaginare che al personale della banca abilitato alla gestione dei conti correnti venga dato l'accesso alle informazioni sul saldo di un qualsiasi conto corrente. Ovviamente questi utenti particolari avranno una pagina di ingresso diversa che, in qualche modo, consente di visionare l'intero elenco dei correntisti e dei loro conti correnti. Ogni conto verrà presentato come ancora HTML del tutto simile a quelle che abbiamo visto, per esempio:

<a href="saldo?cc=123">123</a>

La nostra pagina attiva "saldo" deve essere modificata: il controllo iniziale della identità dell'utente che l'ha invocata deve ora prevedere anche gli impiegati della banca addetti ai conti correnti. La logica di controllo si sta complicando ulteriormente, e anche qui dobbiamo verificare che l'utente sia abilitato quando tale determinazione l'abbiamo già fatta nel momento stesso in cui abbiamo presentato all'operatore l'ancora per accedere alla pagina del saldo.

Immaginiamo ora che la banca debba estendere ulteriormente l'accesso alle informazioni sui conti correnti. Per esempio, la banca potrebbe decidere di autorizzare un determinato promotore finanziario a visionare i conti correnti appartenendi a correntisti della propria zona territoriale di competenza allo scopo di rivolgere loro eventuali proposte di investimento. Anche per questi operatori dovremo prevedere di utilizzare la nostra pagina attiva "saldo", ma dovremo anche prevedere un ulteriore ridondante controllo della validità dell'accesso.

Programmazione WEB funzionale

I problemi incontrati nell'esempio sono legati alla mancanza di un concetto di stato interno dell'applicazione WEB e alla presenza di una informazione (il codice di conto corrente) che viaggia inutilmente tra client e server. Invece ciò che vorremmo è mantenere sul server memoria degli elementi di interazione presentati all'utente, e tenere anche memoria delle funzionalità da invocare per ogni elemento di interazione azionato.

Consideriamo una tipica applicazione stand-alone con interfaccia grafica. L'applicazione presenta all'utente una serie di informazioni e di elementi di interazione (control). Quando l'utente agisce su particolari control (bottoni, ancore, ecc.) l'applicazione reagisce, esegue una elaborazione dipendente da uno stato interno, e restituisce la risposta sotto forma di una nuova schermata aggiornata.

Gli strumenti di sviluppo utilizzati per questo genere di applicazioni mettono a disposizione del programmatore un meccanismo degli eventi oppure un sistema per associare ai vari control una funzione del programma, così semplificando il lavoro di sviluppo.

Ci possiamo chiedere ora se sia possibile riprodurre anche nelle applicazioni WEB un comportamento analogo. La risposta è senzaltro positiva se si predispone lo strumento di programmazione adatto. Questo strumento di programmazione dovrebbe conservare sul server una associazione tra ciascun controllo presente nella pagina WEB che l'utente sta vedendo (sia esso un'ancora o un bottone), e la corrispondente funzione da chiamare qualora tale controllo venga azionato. La struttura dati sul server che conserva queste informazioni si chiama registro di stato WEB.

Il Registro di stato WEB

Il meccanismo qui proposto per la gestione dello stato della sessione prevedere una struttura dati che abbiamo denominato "registro di stato WEB" o più brevemente "registro". Il registro di stato WEB è una tabella che contiene tre colonne. La prima colonna contiene il numero ordinale della riga. La seconda colonna contiene la funzione di chiamata in avanti. La terza colonna contiene la funzione di chiamata all'indietro. Preoccupiamoci per il momento di chiarire il significato delle prime due colonne, rimandando la spiegazione della terza ai paragrafi seguenti.


i Chiamata in avanti Chiamata all'indietro
1 saldo(cc=128)  
2 saldo(cc=456)  
3 saldo(cc=789)  

Figura 2. Aspetto del registro di stato WEB dopo l'esecuzione della funzione menu() che ha presentato all'utente l'elenco dei suoi conti correnti. La colonna dell'indice i riporta i valori inviati al client per ogni ancora. La colonna delle chiamata in avanti sono le corrispondenti funzioni da richiamare.


Per ogni sessione avremo un registro di stato WEB. Il registro contiene l'associazione tra le ancore e i bottoni messi a disposizione dell'utente nella pagina correntemente visualizzata e le funzioni da invocare se tali controlli vengono azionati. Ogni controllo verrà fatto corrispondere ad una specifica entrata nella tabella del registro.

Ritorniamo al nostro esempio di WEB banking. La pagina di ingresso del correntista conteneva tre conti correnti. La funzione del sito che ha generato questa pagina ha anche generato il registro di stato WEB mostrato in figura 2. Il codice inviato al client assume questo aspetto:

<a href="rh?i=1">123</a>
<a href="rh?i=2">456</a>
<a href="rh?i=3">789</a>

Osserviamo che dal codice HTML sono spariti tutti i riferimenti alla pagina attiva saldo e sono spariti anche i parametri con i quali tale pagina veniva chiamata. Al loro posto viene invocata una apposita pagina attiva denominata rh, sigla che sta per "request handler" (gestore della richiesta). Il request handler deve essere richiamato con un indice i corrispondente alla entrata del registro da richiamare.

Dunque, se l'utente clicca ad esempio sulla prima ancora, il request handler viene invocato con parametro i=1. Il request handler recupera dal registro di stato WEB l'entrata numero 1 ed invoca la funzione del programma indicata nella colonna delle chiamate in avanti. Nel nostro caso verrà invocata la funzione

saldo(cc=123)

che si occuperà di rispondere al client con la pagina del saldo del conto di codice "123". Diversamente dalla implementazione banale precedente abbiamo diversi effetti benefici:

Lo stack WEB

Un altro interessante concetto mutuato dalle applicazioni stan-alone è quello di stack. Per spiegarne l'utilità e il meccanismo di funzionamento ritorniamo all'esempio della funzione saldo(). Nella implementazione banale della funzione l'utente poteva cliccare sull'apposito bottone "indietro" previsto da qualsiasi browser per ritornare alla pagina di ingresso. In alternativa, alcuni preferiscono presentare un'ancora o un bottone nella pagina che rimanda alla pagina precedente sfruttando opportunamente un brano di codice JavaScript. Questo meccanismo è inapplicabile nel nostro modello di programmazione WEB funzionale, dato che il client vedrà come unica pagina del sito il request handler. Inoltre, un simile meccanismo non è compatibile con la necessità di costringere l'utente a seguire un percorso definito tra le varie pagine del sito. Pertanto se vogliamo prevedere nell'interfaccia un control che permetta di andare in una data pagina, allora dovremo prevederlo esplicitamente.

La soluzione sembra dunque essere quella di prevedere nella finestra del saldo un'ancora o un bottone che permettono di saltare alla finestra di ingresso. Il bottone o l'ancora in questione potrebbe essere contrassegnato dalla dicitura "OK", "Indietro" o "Chiudi". Tuttavia questa soluzione banale non ci permetterebbe di riutilizzare la stessa funzione per richiamarla da altri contesti: l'impiegato della banca addetto ai conti correnti non desidera "ritornare" alla finestra di ingresso dell'utente, ma alla propria finestra di consultazione con maggiori funzionalità.

Più in generale, sarebbe utile poter scrivere una serie di funzioni e sotto-funzioni richiamabili da un qualsiasi contesto, capaci di svolgere il proprio compito e quindi capaci di ritornare al chiamante, qualunque esso sia. Ci serve insomma un concetto di stack degli "indirizzi di ritorno" simile, sotto molti aspetti, allo stack utilizzato dagli ordinari linguaggi di programmazione.

La funzione menu() individua l'identità dell'utente in base alla sessione attiva, e quindi presenta all'utente l'elenco dei conti correnti posseduti. Questa volta, però, per ogni controllo proposto al client inseriamo nel registro di stato WEB non solo la chiamata in avanti, ma predisponiamo anche la chiamata all'indietro. In questo caso, la funzione menu() predispone sè stessa come chiamata all'indietro. Il registro di stato WEB assume pertanto questo aspetto:


i Chiamata in avanti Chiamata all'indietro
1 saldo(cc=128) menu()
2 saldo(cc=456) menu()
3 saldo(cc=789) menu()

Figura 3. Aspetto del registro di stato WEB dopo l'esecuzione della funzione menu() che ha presentato all'utente l'elenco dei suoi conti correnti. Rispetto alla figura 2, qui abbiamo predisposto anche i valori della colonna delle chiamate all'indietro.


Il meccanismo di funzionamento del request handler è leggermente diverso quando il registro contiene un chiamata all'indietro. Se l'utente clicca, ad esempio, sulla prima ancora, il request handler verrà chiamato con indice i=1. Questo indice individua la prima entrata nel registro (v. figura 3) che contiene sia una chiamata in avanti che una chiamata all'indietro. Pertanto, il request handler ricopia la chiamata all'indietro menu() in cima allo stack, e solo dopo esegue la chiamata in avanti saldo(cc=123). L'aspetto finale dello stack è rappresentato in figura 4.


Figura 4. Aspetto dello stack dopo la chiamata della funzione salva(). La funzione in cima allo stack è quella che verrà richiamata quando si eseguirà il ritorno.


Infine, la funzione saldo() può ora prevedere il ritorno al chiamante. A questo scopo, essa può presentare un'ancora o un bottone che causano la chiamata ad una apposita funzione di ritorno "return()". Questa funzione estrae la funzione indicata sulla cima dello stack, e quindi la invoca. Nel nostro caso ciò produce il desiderato effetto di riportare l'utente alla sua pagina di ingresso.

L'utilità dello stack è di rendere indipendente la funzione saldo() dalle funzioni che la possono richiamare.

Conclusioni

Ci siamo limitati ad una breve introduzione alla tecnologia della programmazione WEB funzionale concentrandoci sull'aspetto di sicurezza, e abbiamo mostrato come sia possibile superare i problemi posti inizialmente utilizzando questo strumento. Lo stesso strumento apre poi altre interessanti possibilità, quali lo sviluppo veloce di applicazioni WEB complesse, la validazione dei dati, la costruzione di interfacce amichevoli, la realizzazione di funzioni e moduli di libreria riutilizzabili per le proprie applicazioni WEB.

Bibliografia

NOTA: i documenti RFC sono reperibili in www.rfc-editor.org.

Argomenti correlati


Umberto Salsi
Commenti
Contatto
Mappa
Home / Indice sezione
Still no comments to this page. Use the Comments link above to add your contribute.