Approccio Programmatico alla Sicurezza Applicativa

Da Raffaele Rialdi, Visual Developer - Security MVP

Chiunque abbia affrontato il problema della pianificazione della sicurezza in una applicazione si sarà accorto che il problema è tutt’altro che banale. A prima vista l’impresa sembra utopistica e priva di una metodologia tale da poter garantire dei risultati accettabili.

Microsoft al suo interno è tra le aziende che ha lavorato più attivamente in questa direzione e ha già maturato un approccio programmatico e dei risultati che dimostrano che invece è possibile. Lo scopo di questo articolo è quello di analizzare gli elementi fondamentali per gestire la sicurezza di un’applicazione in tutte le fasi del suo ciclo di vita con un approccio programmatico.

La difficoltà di questo processo sta nel fatto che la maggior parte della produzione del software è di tipo artigianale e le software house che hanno un processo produttivo assimilabile a quello industriale sono molto poche.

Nel software ciò che per l’industria è un prototipo, è invece la prima versione di un prodotto finito. Anche in grossi pacchetti software, soprattutto i gestionali, le personalizzazioni sono tante e tali da non poter classificare l’attività con quello che comunemente è inteso come processo di produzione industriale.

In un processo produttivo industriale, l’identificazione dei parametri che identifichino la qualità e la sicurezza di un prodotto è certamente più semplice. Nel prodotto artigianale la continua evoluzione e le personalizzazioni necessarie rendono questo compito molto complesso.

Per esempio lo sviluppatore ha un impatto sul risultato finale decisamente superiore a quello di un operatore in una fabbrica. Al di là delle buone regole di programmazione il programmatore è coinvolto in un processo creativo che incide in modo molto profondo sulla qualità e perciò sulla sicurezza del prodotto finito.

Dobbiamo quindi arrenderci al fatto che anche la sicurezza sia completamente lasciata all’arbitrio dell’artigiano come fosse una sua dote artistica? Certamente no, sempre che l’intero ciclo di vita dell’applicazione venga gestito secondo delle metodologie e degli indicatori tali da poter costantemente monitorare gli elementi che hanno un impatto sulla sicurezza.

Perché scrivere codice sicuro

La domanda sembra banale, eppure a guardare il panorama delle applicazioni presenti sul mercato non è così, tanto più che l’illustre Gartner scrive che ormai più del settanta per cento delle vulnerabilità sono a livello applicativo e non più sull’infrastruttura di rete.

Oggi viviamo un momento in cui il mirino è puntato soprattutto sulle applicazioni web. Sono queste infatti a offrire un’appetibile superficie di attacco visto che ogni singola pagina costituisce un potenziale punto di ingresso per l’hacker, alla continua ricerca di trovare una vulnerabilità. Pensare però che la sicurezza si riduca ad una partita sul web è un grosso errore.

Se l’hacker vuole conquistare una specifica risorsa presente su un sito web, quello è e rimarrà il suo obiettivo ed è pacifico che debba essere difeso a dovere. Ma se una volta il sito web era un prezioso gateway per estendere la conquista al web server, con le odierne tecnologie è sempre meno vero.

Prendiamo un’applicazione Asp.net che giri sotto Internet Information Server 6. Le credenziali del worker process di default sono quelle dell’utente Network_Service che ha i minimi privilegi necessari a far girare l’applicazione web, e se anche l’hacker riuscisse a eseguire codice arbitrario sotto questo account, i danni sarebbero certamente molto limitati. Questo non significa che lo sviluppatore web possa rilassarsi e scrivere codice meno sicuro. La conquista di una applicazione web da parte dell’hacker è comunque una gravissima disfatta. Però l’aver rafforzato la sicurezza sul web sposta necessariamente l’attenzione su tutto il resto e l’attacco dall’interno della LAN è una delle attrattive più forti.

Prendiamo ora il classico gestionale: si può dire esente da problemi di sicurezza perché lavora solo in LAN? Certamente no, in quanto gli attacchi dall’interno della LAN sono in continuo aumento, siano essi attacchi consapevoli o inconsapevoli, cioè veicolati da utenti che scaricano e installano malware da internet. Quanto danno possono fare i privilegi di un utente nella LAN? Guardiamo per un attimo il problema solo dal punto di vista applicativo e pensiamo ad esempio se sia possibile per un utente collegarsi direttamente al database aziendale tramite Access e scrivere dentro le tabelle. Purtroppo questa è ancora una realtà troppo diffusa.

Per tanti anni la sicurezza applicativa è stata sempre e solo vista come un inutile costo e oggi sta diventando invece un parametro che in futuro avrà sempre di più un peso decisionale nell’acquisto di un software, non solo per l’aumentato rischio ma anche per le leggi sulla privacy che impongono un radicale cambio di rotta.

Uno dei concetti fondamentali in sicurezza è che non può essere considerata una caratteristica del software. Essa è trasversale, parte dall’architettura, passa dalla progettazione, si concretizza nello sviluppo e trova conferme nei test.

Sviluppare codice sicuro non implica l’adozione di specifiche soluzioni architetturali ma è indubbio che alcune di queste semplifichino il problema sicurezza.

 

Architettura e sicurezza

Nel corso degli anni la più classica architettura client-server ha dimostrato tutti i suoi punti deboli per scalabilità, tempi di down per manutenzione, potenza hardware necessaria. Sul fronte sicurezza le cose non vanno certamente meglio e si va dall’esposizione del data server a quella di servizi come il file sharing o il catalogo di Active Directory le cui modalità di gestione della sicurezza sono eterogenee, o ancora alla presenza sul client di tutta la logica di business attaccabile con il dump del processo, in poche parole una maggiore superficie di attacco.

La prima e più semplice evoluzione è una architettura multilivello che vede almeno la suddivisione in presentation layer, business layer e data layer (il database), ma non si ferma necessariamente a questi tre.

La strutturazione a più livelli è vincente per funzionalità, scalabilità, e un alto livello di disaccoppiamento, tipico della più recente visione SOA (Service Oriented Architecture), permette anche una gestione più agile dell’intero ciclo di vita dell’applicazione oltre a poter evolvere le versioni di ogni singolo layer separatamente, eventualmente gestite da team differenti. Queste stesse motivazioni sono degli ottimi punti di partenza per costruire un’architettura sicura, a mio avviso irrinunciabili.

In quale modo disaccoppiamento e architettura multilivello giovano alla sicurezza?

 

I componenti del sistema

La suddivisione a più livelli comporta necessariamente l’individuazione dei componenti fondamentali del sistema che andranno collocati nei diversi livelli dell’applicazione. In questo scenario il componente non necessariamente coincide con il componente COM o del Framework.NET ma è una definizione molto più generica che può includere questi così come un Web Service o un servizio di Windows Communication Foundation, in sostanza un macroblocco del sistema.

Ad esempio quando viene pianificata un’architettura è indispensabile pianificare quali sono le risorse gestite dall’applicazione che devono essere difese e strutturare i componenti del sistema in modo tale da minimizzare i rischi.

Questo processo, compresa la chiara identificazione delle risorse da difendere, permette di individuare i privilegi minimi necessari al componente per poter svolgere il suo compito. È importante ricordare sempre che il codice viene eseguito in uno specifico contesto di sicurezza nei confronti del sistema operativo che è quello determinato dal token associato al processo o al thread che esegue quel codice. Ben diverso è il contesto derivato dall’utente che ha richiesto l’esecuzione di quel codice. Generalmente nelle applicazioni Windows questi due contesti coincidono ma è tipicamente non vero in applicazioni Asp.net a meno di non utilizzare impersonation o delegation il cui approfondimento va al di là dello scopo di questo articolo.

L’individuazione dei privilegi minimi del componente è strategica per individuare il livello di isolamento dei componenti. In Windows il processo rappresenta il confine dentro il quale il codice viene isolato dal punto di vista della sicurezza. Il codice gestito del Framework.Net restringe questo confine nell’application domain. Perciò ogni livello che venga isolato in un processo se costituito da codice nativo o da un application domain se creato in codice gestito, garantisce a isolare l’eventuale minaccia in quel layer e limitatamente ai privilegi assegnati alle credenziali associate al processo. Questo è uno dei tanti esempi che dimostrano che se la sicurezza non è pensata fin dall’architettura iniziale, porre rimedio può avere un impatto difficilmente colmabile.

 

I ruoli degli utenti

L’aver stabilito i privilegi minimi per ogni componente della nostra applicazione ha come effetto primario l’importante riduzione della superficie di attacco.

Non meno importante è il beneficio di centralizzare il controllo di accesso alle risorse a prescindere da quale sia soggetto che li eroga. La gestione della sicurezza di risorse eterogenee (DB, Active Directory, file, apparati interfacciati, etc.) è prona agli errori e richiede una gestione manuale dell’amministratore di sistema che richiederebbe degli audit specifici paritetici ai test sul software, cosa che difficilmente viene messa in pratica. La soluzione a questo problema sta nel definire dei ruoli applicativi che svolgeranno il compito più comunemente svolto dai ruoli di accesso alle risorse. Oggetti e dati relazionali sono ortogonali tra di loro e visto che l’infrastruttura e i linguaggi che utilizziamo sono profondamente radicati sulla teoria di programmazione orientata agli oggetti, i ruoli applicativi si sposano meglio con la logica di business nella gestione della sicurezza. In questo scenario la sicurezza delle risorse servirà a delimitare il massimo raggio di azione dell’applicazione indipendentemente dal contesto dell’utente, per esempio impedendo la cancellazione fisica dei dati.

Per esempio il business layer della nostra ipotetica applicazione esporrà metodi come GetCustomer o GetCustomerReport che restituiranno rispettivamente i dati del cliente e un file Excel con le sue statistiche, ma solo agli utenti del ruolo applicativo ‘vendite’. La gestione per l’accesso ai dati del database o al file system per creare e leggere il file xls non saranno demandati al data server o al file system ma al business layer stesso. Il vantaggio non è solo in chiave di semplificare l’accesso alle risorse, paradigma che ha comunque sempre giovato alla sicurezza, ma anche per le note limitazioni e debolezze di impersonation e i rischi collegati all’apertura di delegation.

 

L’aiuto del Framework

In questo frangente la Code Access Security del Framework ci mette a disposizione due importanti strumenti che semplificano la gestione della sicurezza appena descritta: la sicurezza imperativa e dichiarativa. I conoscitori di Asp.net già avranno avuto modo di provare la loro comodità visto che le applicazioni Web si configurano per definizione come applicazioni di almeno tre livelli: la presentazione sul browser, la business logic nell’applicazione e il data server. Quando un utente viene autenticato dalla web application, questa ha modo di eseguire un controllo imperativo sulla sua appartenenza o meno a un certo ruolo: sto parlando del metodo IsInRole che ci indica l’appartenenza o meno di un utente a un ruolo (o un gruppo nel caso si usi la Windows Authentication). Questo è il metodo usato dal controllo LoginView di Asp.net 2.0 per generare pagine differenti a seconda dei privilegi assegnati all’utente. Per esempio un amministratore potrà fruire di un’interfaccia più ricca di menu rispetto a un semplice utente. Allo stesso modo una Windows Application potrà nascondere uno user control oppure disabilitare dei controlli.

Il controllo imperativo appena descritto è solo la prima fase. L’utente sceglierà per esempio di effettuare la cancellazione di un certo elemento e a questo punto la nostra applicazione non dovrà contare sul fatto che solo gli utenti che appartengono al ruolo corretto possano accedere a certe funzionalità ma sarà necessario eseguire un secondo controllo per evitare che un hacker abbia manomesso il messaggio dell’interfaccia grafica verso la nostra applicazione. Questo secondo controllo è un controllo dichiarativo che viene effettuato apponendo un attributo sul metodo che accede alle risorse. L’attributo garantisce che solo gli utenti dei ruoli specificati possano causare l’esecuzione del metodo, e una SecurityException in caso contrario.

Ma la sicurezza di un’applicazione può limitarsi a queste indicazioni? Quanto detto fin’ora sono i punti fondamentali attorno ai quali costruire una metodologia che permetta di creare un’applicazione con una architettura e un codice più sicuro. Prendiamo ora in esame un esempio di metodologia che usi questi principi.

 

La metodologia SDL

SDL o Secure Developmente Lifecycle è una metodologia creata nel 2002 (http://news.com.com/2009-1001-817210.html) e usata da Microsoft al suo interno, allo scopo di diminuire le vulnerabilità di un’architettura software.

I punti essenziali di questa metodologia sono la creazione di un modello di minacce (threat modeling), l’uso di strumenti di analisi statica del codice, le revisioni tecniche e la verifica del codice. La presenza di un “security advisor”, una persona incaricata alla politica della sicurezza, garantisce una visione globale e oggettiva su queste problematiche.

SDL ha quattro principi fondamentali chiamati SD3+C:

  • Secure by Design. È un concetto secondo il quale architettura, progettazione e implementazione devono essere pensati per essere sicuri.

  • Secure by Default. Esprime la necessità per tutti i componenti del sistema di dover offrire la minore superficie di attacco possibile usando il principio del “least privilege” cioè di richiedere un profilo di privilegi che sia il più basso possibile.

  • Secure in Deployment. Determina la necessità di evidenziare le condizioni necessarie in fase di installazione e la documentazione necessaria per evitare errori nell’infrastruttura

  • Communications. Indica come mandataria la necessità di eseguire frequenti incontri tra i componenti del gruppo di sviluppo documentando vulnerabilità e contromisure; indica inoltre come strategica la prontezza nel reagire proattivamente ai problemi eseguendo un riesame dell’architettura e delle implementazioni.

Si nota subito come questi principi si sposino perfettamente con quanto detto in precedenza. SDL naturalmente fornisce numerosissimi indicazioni per ogni ruolo del team di sviluppo. Per esempio viene preferito l’uso di linguaggi strong-typed, tool di analisi statica del codice come Prefix, PreFast e FxCop (Code Analisys in Visual Studio 2005) e strumenti che eseguano test fornendo input non valido all’applicazione (fuzzing). Non meno importante è l’accento che viene posto sulla revisione del codice per le classi che usano le ACL, gli attributi della Code Access Security o alla ricerca di calcoli aritmetici che possano dare luogo a overflow o underflow.

L’insieme delle attività di sicurezza che sono state stabilite per l’applicazione culminano prima nel “security push”, il primo momento in cui l’applicazione è sufficientemente matura per essere testata dal punto di vista della sicurezza, e successivamente nella “Final Security Review”, momento nel quale si tirano le somme sui risultati che sono stati ottenuti sottoponendo l’applicazione a specifici test di penetrazione e dove si è ancora in tempo per reagire rapidamente eseguendo le modifiche necessarie.

Una trattazione esaustiva di SDL va al di là dello scopo di questo articolo e per chi fosse interessato l’argomento è trattato in modo completo nel libro “The Security Development Lifecycle: SDL: A Process for Developing Demonstrably More Secure Software”, Michael Howard e Steve Lipner, ISBN 0-7356-2214-0.

Il vantaggio di applicare SDL rispetto ad altre possibili metodologie è di essere stata sperimentata e applicata con successo da Microsoft che ha ottenuto risultati misurabili come ad esempio SQL server 2000 SP3, Windows XP SP2 o Windows 2003 SP1 (https://msdn2.microsoft.com/library/ms995349.aspx). Inoltre SDL è sufficientemente plastica da poter essere utilizzata anche in software house di modeste dimensioni e applicabile anche per chi applica metodi di sviluppo Agile compresa Extreme Programming.

A prescindere dall’applicare o meno in modo completo quanto viene indicato in SDL, i concetti di base già esposti rimangono un punto di partenza fondamentale su cui costruire il modello specifico dell’applicazione che si vuole progettare in sicurezza.

 

Threat Analysis and Modeling Tool

Non può esistere un tool che miracolosamente risolva il problema della sicurezza di una applicazione. La premessa è d’obbligo per non illudere né ingannare il lettore. Fino ad ora abbiamo descritto quali siano i punti essenziali per realizzare una architettura che semplifichi la creazione di una applicazione più sicura. In questo processo sono emersi dati tangibili che permettono di identificare con maggiore facilità i punti di maggiore rischio.

L’utilità di uno strumento sta nel raccogliere le informazioni sulla struttura dell’applicazione, i punti di ingresso, i ruoli, i componenti e metta in evidenza le possibili vulnerabilità così che architetto e sviluppatori possano individuare subito i punti di attenzione. E questo è quanto è stato realizzato da un gruppo di sviluppo chiamato ”Ace Team” (https://blogs.msdn.com/ace_team) che in Microsoft si occupa di problemi riguardanti la sicurezza, le performance e la privacy.
Il “Microsoft Threat Analysis and Modeling Tool” (https://blogs.msdn.com/threatmodeling/) è volutamente slegato dal sistema operativo, dall’uso di specifici linguaggi, dall’uso di codice nativo o gestito, dall’applicazione di tutte le indicazioni di SDL, dall’uso di Visual Studio pur permettendo l’esportazione dei work items per l’edizione team suite.

Come dice il nome “threat modeling”, questa applicazione grafica permette di modellare le minacce che possono arrivare alla nostra applicazione raccogliendo i dati che sono emersi durante la definizione dell’architettura ed evoluti in fase di progettazione e sviluppo.

Nella fase iniziale il wizard di questo tool raccoglie i dati:

  1. I ruoli applicativi. Vengono immessi i nomi dei ruoli in cui sono classificati gli utenti che usano l’applicazione.

  2. Le risorse (dati). Si inseriscono tutte le tipologie di dati utilizzate dall’applicazione, siano esse dati di un DB, file, etc.

  3. Matrice di Accesso. Per ciascun ruolo si definisce quale tipo di accesso è possibile eseguire sulle risorse. Per tipo di accesso si intende il classico creazione, lettura, modifica, cancellazione (CRUD).

  4. Use Cases. Questo passo permette di stabilire quale sia il tipico uso fatto dagli utenti per accedere ai dati.

  5. Componenti di sistema. Vengono identificati con un nome i macroblocchi del sistema di cui abbiamo parlato in precedenza.

  6. Rilevanze. Sono dei punti di attenzione particolari come l’uso di codice nativo piuttosto che di generazione di codice o ancora la costruzione di query SQL o LDAP.

  7. Le chiamate. Si identificano tutte le azioni che un utente, per esempio grazie all’interfaccia utente, può eseguire nei confronti della nostra applicazione.

  8. Le minacce. Si stabiliscono quali siano le potenziali minacce che possono occorrere.

Oltre al vantaggio di correlare facilmente le informazioni che vengono immesse, il tool viene installato con una libreria estendibile con le più diffuse Minacce e Rilevanze.

L’immissione dei dati non è certamente semplice ed è un lavoro molto sostanzioso che però è ripagato da una serie di report con tanto di grafici con gli “use cases” costruiti in modo automatico grazie al motore di Visio.

I report, pensati per essere consegnati ai team che si occupano delle diverse parti del progetto, in alcuni casi contengono le indicazioni che il gruppo del progetto deve seguire scrupolosamente.
Per esempio il team di sviluppo riceve, componente per componente, delle indicazioni contestuali alla rilevanze. Nelle indicazioni sono presenti snippet di codice o link per eseguire l’implementazione nel modo corretto contrastando così la possibile vulnerabilità:

TAM_DevReport.png

Il team di test a sua volta ottiene un report sulle specifiche funzionalità e le modalità di svolgimento dei test da effettuare:

TAM_TestReport.png

Ed ancora il report di riepilogo contiene una dettagliata struttura dell’intera applicazione comprensiva di Use Cases, chiamate, e quant’altro descritto nel modello.

TAM_CallTrustFlow.png

TAM_AccessMatrix.png

Infine è possibile estendere il tool con un sistema a plugin. Recentemente sul blog threatmodeling prima citato è stato pubblicato il plugin di risk measurement che calcola il rischio come prodotto della probabilità per l’impatto (R = I * P) dove l’impatto è in una scala di tre livelli (alto, medio, basso).

 

Conclusione

Chi conosce il processo di certificazione di qualità del software avrà notato quanto il processo programmatico di pianificazione della sicurezza vada nella stessa direzione di normative come la ISO9001:2000. Lo stesso “Threat Analysis and Modeling Tool” va in questa direzione e i report che vengono creati dal tool sono in perfetta sintonia con quelle normative, non solo per la gestione della sicurezza ma per il progetto nella sua totalità.

Il vantaggio di un approccio programmatico è quello di poter definire dei parametri di valutazione della sicurezza del software anche quando il suo processo di creazione sia identificato artigianale e a maggior ragione quando sia industriale.

Mi piace pensare che la sicurezza sia un motivo più che valido per scegliere una architettura e delle metodologie di sviluppo come quelle descritte, ma se anche così non fosse, gli altri motivi sono certamente così allettanti da essere presi come motivo trainante.