Without requirements or design, programming is the art of adding bugs to an empty text file. - Louis Srygley


Introduzione

Iniziamo il nostro excursus richiamando alcuni aspetti chiave della sicurezza informatica. Una componente molto importante dell'informatica, almeno nell'accezione tradizionale, è rappresentata dal codice. Nel corso della sua ricca storia - un primo esempio lo si ha intorno al 1842 con Ada Lovelace ed il suo metodo per calcolare i numeri di Bernoulli con l'Analytical Engine - la programmazione ha portato a grandi miglioramenti su diversi ambiti, guidando lo sviluppo delle industrie. Infatti, dai viaggi spaziali alle reti delle telecomunicazioni agli aspetti più comuni della vita quotidiana, possiamo certamente affermare che tutto è stato rivoluzionato ed influenzato dal codice. Tuttavia quest'ultimo è scritto dai programmatori, ed è risaputo che errare è umano! In molte circostanze, infatti, non mancano i malfunzionamenti, gli errori, i difetti, le vulnerabilità - tutti questi fattori possono essere raggruppati sotto il nome di bug - ad accompagnare le feature del software. Da quì potrebbe immediatamente nascere un primo accenno alla definizione di cybersecurity: un insieme di metodologie che permettano di identificare e risolvere eventuali bug, ma che soprattutto puntino alla loro prevenzione.

Designed by pikisuperstar on Freepik

In questa sezione vedremo le principali categorie di problemi che è possibile incontrare quando ci cimentiamo a scrivere codice - spesso ci dimentichiamo del concetto di sicurezza quando siamo intenti a programmare - ed analizzeremo alcune soluzioni che permettono di prevenire e individuare i bug, allo scopo di evitare che potenziali vulnerabilità possano venire exploitate.

Problema

Il recente emergere di piattaforme di sviluppo multi-esperienza, utilizzate nello sviluppo di chat, realtà aumentata (AR) e dispositivi indossabili a supporto del business digitale, sta portando il mercato del software a puntare il mirino sui settori del mobile e del web, come afferma Jason Wong, vice presidente della ricerca presso Gartner:

Development platform vendors are expanding their value proposition beyond mobile apps and web development to meet user and industry demands.

Questo significa che vi deve essere particolare attenzione alla sicurezza se si sviluppano prodotti per questi due settori. Per descrivere meglio le principali problematiche di sicurezza che riguardano prevalentemente la programmazione, introduciamo il Software Development Life Cycle (SDLC), ovvero l'insieme delle fasi che compongono il processo di sviluppo di un software:

  • Raccolta ed Analisi dei Requisiti
  • Progettazione
  • Implementazione
  • Testing
  • Deployment
  • Manutenzione

Già nella prima fase si potrebbero incontrare le prime difficoltà che, in maniera diretta o indiretta, possono portare alla formazione di un bug. Riuscire a comprendere le specifiche dei requisiti dal linguaggio naturale - che a differenza di un linguaggio formale comporta ambiguità - del cliente rappresenta infatti uno dei principali problemi che si possono riscontrare nella fase di Raccolta e Analisi dei Requisiti. Ciò è dovuto prevalentemente dalla non parziale (o totale) conoscenza sia da parte dei progettisti/programmatori riguardo il settore in cui opera il committente, che da parte di quest'ultimo circa lo sviluppo software. Ricordiamo a tal proposito che, utilizzando un processo di sviluppo AGILE, gli sviluppatori saranno comunque in grado di aggiornare in brevi tempi il sistema software in base alle richieste di modifica del cliente, colmando alcune discrepanze.
La fase di Progettazione di un sistema software è spesso sottovalutata, in particolare da programmatori alle prime armi. Non per nulla è bene affidare questo compito ad esperti, i cosiddetti ingegneri del software. Una cattiva progettazione può portare, oltre a malfunzionamenti ed errori vari, anche a serie vulnerabilità.
Durante la fase di Implementazione gli sviluppatori si ritrovano spesso a lavorare per giorni su una parte del software e, per pressioni tempistiche o dimenticanza, non effettuano - o spesso effettuano in maniera blanda - una pratica fondamentale: il debugging. Cercare potenziali bug su molte linee di codice è ovviamente più difficoltoso che farlo a poco a poco e su un minor numero di linee. In questi casi infatti, nonostante i controlli del codice, potrà capitare di tralasciare anche un piccolo bug che potrebbe rivelarsi una grande vulnerabilità dell'intero sistema. Un altro problema presente in questa fase è riconducibile alla scarsa conoscenza dei tool e framework utilizzati, i quali potrebbro, in aggiunta alle circostanze, portare alla presenza - anche involontariamente - di vulnerabilità.
Una delle fasi fondamentali in un processo di sviluppo software è costituita dal Testing. In molti casi, purtroppo, quest'ultimo viene bypassato perché semplicemente si ha la "presunzione" di aver progettato ed implementato il codice in maniera perfetta ed impeccabile. In altre circostanze invece, il testing viene effettuato ma in modo superficiale: vengono testate solo le parti (ritenute) principali dell'intero sistema software, tralasciando così porzioni di codice che potrebbero contenere malfunzionamenti o errori o vulnerabilità. È doveroso osservare che non è possibile effettuare tutte le possibili combinazioni di test - ci vorrebbe un tempo esponenziale! - su tutto il codice. Tuttavia, questa precisazione non giustifica un processo di testing parziale del codice, come vedremo poco più avanti.

Designed by katemangostar on Freepik

Le ultime due fasi del SDLC sono il Deployment e la Manutenzione. Una volta che il sistema software viene messo in produzione (deployment), bisogna accettarsi che l'ambiente sia adeguato e "sicuro". Per quanto riguarda la manutenzione, spesso ci si dimentica di fornire supporto continuo con periodici aggiornamenti al sistema software. In particolare gli update - e ancora più i system update - rappresentano un punto cruciale per prevenire e, soprattutto, inibire vulnerabilità note.

Esempio

Nella storia della programmazione possiamo trovare una vasta gamma di vulnerabilità - molte delle quali gravi - dovute a errori commessi in fase di sviluppo del sistema software. L'argomento di cui parliamo adesso ne rappresenta uno dei più gravi e famosi esempi: Heartbleed. Trattasi di una seria vulnerabilità nell'utilizzata libreria di software crittografico OpenSSL. Registrato come CVE-2014-0160, Heartbleed affligge le versioni di OpenSSL dalla 1.0.1 alla 1.0.1f ed ha compromesso tutte le applicazioni che ne facevano uso per oltre 2 anni. Il bug permette ad un potenziale attaccante di leggere fino a 64 KB dalla memoria del server, la quale può contenere informazioni come richieste HTTP inviate al server (Cookie, credenziali inviate tramite form, User-Agent ed altri header), risposte HTTP inviate da quest'ultimo, chiavi crittografiche ed altri dati sensibili, quale ad esempio il contenuto di e-mail.
Il bug è causato da un errore di programmazione presente nell’estensione TLS Heartbeat di OpenSSL che costituisce un’implementazione dell’RFC6520. Questa estensione permette di mantenere attiva una connessione TLS senza un continuo trasferimento di dati. L'attacco è molto semplice concettualmente: il client può ingannare il server facendogli credere di aver inviato un payload di dimensione maggiore rispetto a quello effettivamente inviato. Di conseguenza, il server costruirà la Heartbeat response della stessa dimensione (payload length) comunicata dal client, inviando a quest'ultimo quantità di memoria in eccesso che potrebbe contenere dati random utilizzati precedentemente dal server.

Full Vignettes here

Risulta quindi evidente che un attaccante può reiterare questo procedimento più volte in modo da ottenere sempre un maggior numero di informazioni dalla memoria del server, con la possibilità di ricavarne qualcuna fra quelle sensibili che abbiamo descritto precedentemente.
Heartbleed mostra come un piccolo e apparentemente banale errore di programmazione ha potuto comportare gravi ripercussioni su scala globale. Sarebbe stato sufficiente effettuare dei test di robustezza sugli input per evitare questa vulnerabilità.

Soluzioni

Nell'ingegneria del software un Design Pattern è una soluzione, generale e riutilizzabile, a un problema che si verifica comunemente in un determinato contesto durante le fasi di sviluppo del software. Al fine di sviluppare un sistema sicuro, oltre ai requisiti funzionali occorre includere i requisiti di sicurezza. Una prima soluzione dunque, ai problemi che abbiamo presentato in precedenza, è rappresentata dall'utilizzo (corretto) dei design pattern. I problemi di sicurezza sono spesso categorizzati tramite lo STRIDE model of threats, il quale riassume i rischi relativi alla sicurezza in sei categorie, ognuna correlata ad una proprietà desiderata:

  • Spoofing -> Authenticity
  • Tampering -> Integrity
  • Repudiation -> Non-repudiability
  • Information disclosure -> Confidentiality
  • Denial of Service -> Availability
  • Elevation of Privilege -> Authorization

Un primo esempio di design pattern orientato alla sicurezza è l'Authenticator, il cui intento è quello di garantire la proprietà di autenticazione. Una variante molto diffusa ed utilizzata di questo design pattern è rappresentata dal Single Sign On (SSO), utilizzato per esempio da Google, che permette ad un soggetto di verificare la propria identità e di riutilizzare il risultato di tale verifica più volte - solitamente su vari domini - per certo intervallo di tempo. Questo ragionamento viene adottato anche dal protocollo per l'autenticazione Kerberos, il cui step iniziale prevede l'autenticazione dell'utente, per mezzo delle credenziali, che ottiene in cambio un ticket-granting ticket (TGT). Quest'ultimo in pratica rappresenta il corrispondente della verifica da esibire per potersi autenticare ad altri domini.
In un ambiente in cui si ha a che fare con dati, specie sensibili - approfondiremo a tal proposito la questione sulla privacy più avanti -, è necessario imporre restrizioni d'accesso. Per questo scopo, è bene ricordare i design pattern Policy Enforcement Point (o Reference Monitor) e Role-Based Access Control, i quali permettono rispettivamente di: definire un’astrazione che intercetti le richieste e ne controlli la conformità con le autorizzazioni; descrivere come gli utenti possono acquisire i diritti in base alle funzioni del loro lavoro o dei compiti loro assegnati.
Se in fase di progettazione possiamo affidarci ai design pattern, in fase di implementazione ci viene in aiuto il Refactoring, ovvero quel processo che consiste nel cambiare un sistema software in modo che i requisiti funzionali soddisfatti rimangano gli stessi ma la struttura interna risulti migliorata. È bene osservare infatti che, in assenza di tecniche di refactoring, la progettazione - sebbene perfetta ed impeccabile - si deteriorerà man mano che si continuerà ad implementare il codice. Poichè si rende quest'ultimo più semplice da leggere ed interpretare, grazie al refactoring è possibile scovare o, addirittura, prevenire potenziali bug.
Procedendo verso la fase di testing, una possibile soluzione alle esponenziali combinazioni di input da controllare, consiste in metodologie di testing che permettono di coprire quella che, nel gergo, prende il nome di t-way interaction - si veda il testing combinatoriale -, ovvero una combinazione di t parametri di interazione che copre i possibili malfunzionamenti.

Designed by katemangostar on Freepik

Abbiamo già ribadito che attualmente il mercato ha gli occhi puntati sul mobile e sul web. È doveroso affermare che per quanto concerne quest'ultimo, è possibile utilizzare i Web Application Firewall (WAF), i quali permettono di applicare una serie di regole a livello applicativo in modo da proteggere i server. Osserviamo inoltre che un WAF può essere considerato come un Reverse Proxy. A questo proposito, vi sono ulteriori design pattern orientati alla sicurezza che si allacciano perfettamente a questo ambito: Firewall, Protection Reverse Proxy, DMZ. In aggiunta, ricordiamo anche che i Web Application Firewall possono essere integrati in soluzioni SIEM - approfondiremo queste ultime fra qualche sezione.

Conclusioni

Per concludere il primo approfondimento nel mondo della sicurezza informatica, in aggiunta ai concetti di Security by Design - si veda anche Security by Design Principles - e Security by default che abbiamo implicitamente descritto, è doveroso citare un ulteriore metodo - utilizzato nei campi dell'ingegneria del software e dell'hardware - per scovare e, soprattutto, prevenire bug: la formal analysis. Senza scendere nello specifico, i metodi formali sono dei particolari tipi di tecniche basate sulla matematica per la specifica, lo sviluppo e la verifica di sistemi software e hardware.


References