Lo sviluppo del software è il mix di design, architettura, programmazione e test. Non avere la sicurezza come requisito porta a un prodotto scadente, perdite di tempo e costi inutili.
Lo sviluppo di un software – tutto ciò che contiene del codice di programmazione, come un eseguibile per computer, sito web, DBMS o applicazione mobile – è una attività onerosa che richiede tempo e investimenti economici.
Le attività di sviluppo sono svolte da professionisti del design, progettazione, programmazione e test, generalmente con notevoli competenze nei relativi settori. La fase di controllo della sicurezza – confidenzialità, integrità, disponibilità – è però troppo spesso ignorata o eseguita con superficialità.
Indice degli argomenti
Perché il Secure Coding?
Il non lavorare secondo le regole e linee guida del Secure Coding può portare a ottenere un prodotto scadente, e quindi a perdere la fiducia di clienti, fornitori e di tutti gli altri stakeholder, oltre a importanti danni economici dovuti a blocchi nelle attività, perdita di dati o ad attacchi informatici. Scoperta la problematica, sarà necessaria poi un’attività di analisi profonda – per comprendere l’errore, quali sono i rischi e le risorse coinvolte – seguita dalla fase di test per validare i risultati ottenuti dall’analisi e da una fase di aggiornamento o riparazione del codice, per poi effettuare nuovamente analisi e test per verificare il miglioramento apportato.
La mancanza di un approccio proattivo è la prima causa di vulnerabilità in un software, la maggior parte derivanti da un numero relativamente piccolo di errori comuni di programmazione. Queste vulnerabilità sono note e largamente documentate su Internet: ciò le rende facilmente sfruttabili da un malintenzionato.
Prevedere la sicurezza nel codice permette quindi di evitare l’introduzione accidentale di vulnerabilità, bug, e malfunzionamenti funzionali e/o logici.
In base alla natura del software, dell’infrastruttura su cui opera e della vulnerabilità, gli impatti possono compromettere il software, i sistemi operativi, i database, l’ambiente condiviso o anche il sistema dell’utente/cliente, e tutte le informazioni associate.
Esempi di vulnerabilità note
A scopo esemplificativo, individuiamo alcune tra le vulnerabilità più conosciute. Gli esempi riportati non sono i problemi più gravi, ma i più comunemente commessi.
- Overflow
- Il Buffer Overflow si verifica quando un processo tenta di archiviare dati oltre il limite fisso prestabilito: se ad esempio è possibile memorizzare solo 10 elementi, i successivi saranno scritti ripartendo dall’inizio della memoria dedicata. In base a come viene gestita la problematica di default, il sistema può bloccarsi, interrompere la scrittura dei nuovi dati, o (solitamente) riscrivere sui vecchi dati.
- Un Integer Overflow si verifica quando un’operazione aritmetica genera un numero troppo grande per essere rappresentato all’interno dello spazio disponibile.
Il rischio è che un attaccante può effettuare un attacco overflow, riscrivendo dati casuali, quindi facendo perdere quelli da proteggere. Il rischio può anche nascere da un processo interno o esterno che opera con regole differenti.
- Percorso imprevedibile
- La Race Condition è il comportamento in cui il risultato di una elaborazione dipende dalla sequenza o dalla tempistica di altri eventi incontrollabili. Diventa un problema quando gli eventi non avvengono nell’ordine previsto dagli sviluppatori.
- La Randomness è la mancanza di un percorso prevedibile degli eventi. Una sequenza totalmente casuale di eventi non ha ordine, quindi non segue una combinazione e modello intelligibili, ma in molti casi è prevedibile la frequenza dei risultati.
Il rischio di un software che non prevede e gestisce correttamente i risultati attesi, dagli input e dalle elaborazioni, può indurre a importanti blocchi e corruzione dei dati, oltre a bug e falle.
- Altri attacchi
Un attacco Format String – nel campo degli ‘injection’ – avviene quando un utente malintenzionato fornisce specifici input che sono interpretati dal software, diversamente dal previsto, inducendo alterazioni o blocchi del funzionamento, o modifiche dei dati – per esempio mediante l’avvio non autorizzato di comandi.
Altre vulnerabilità più legate alla privacy, a cui può essere soggetto un software, possono ad esempio consentire a un attaccante di ottenere l’accesso non autorizzato al sistema, e rubare o modificare le informazioni.
Come intervenire?
Le problematiche di sicurezza del software possono essere introdotte in qualsiasi fase del ciclo di sviluppo del software, ad esempio:
- non identificando in anticipo i requisiti di sicurezza;
- creando progetti concettuali con errori logici;
- utilizzando standard obsoleti, che introducono vulnerabilità tecniche;
- distribuendo il software in modo errato;
- inserendo errori durante la manutenzione o l’aggiornamento.
Prima di iniziare a sviluppare il software è necessario individuare le regole e linee guida essenziali e aderire rigorosamente.
- Design
- Security policies Le politiche di sicurezza devono essere individuate come requisito, e devono essere il primo passo del progetto, con il loro design e architettura; le fasi successive dello sviluppo dovranno implementarle e applicarle. Se, ad esempio, si prevede in anticipo che i privilegi in un sistema devono essere vincolati a persone fisiche per periodi temporali prestabiliti, lo sviluppo del sistema implementerà al meglio le politiche, ovviando a rattoppi post-produzione e perdite di tempo.
- Security requirements Identificare e documentare i requisiti di sicurezza nelle prime fasi del ciclo di sviluppo e assicurarsi che gli elementi sviluppati, successivamente, siano valutati per la conformità con tali requisiti. Quando i requisiti di sicurezza non sono definiti, la sicurezza del sistema risultante non può essere valutata in modo efficace.
- Difesa a cipolla Gestire il rischio con più strategie difensive, in modo che se uno strato di difesa risulta inadeguato, un altro livello di difesa possa impedire che un difetto di sicurezza diventi una vulnerabilità sfruttabile, e limitare le conseguenze di un possibile attacco. Come esempio, la combinazione di tecniche di programmazione sicure abbinate ad ambienti di esecuzione sicuri può ridurre la possibilità che le vulnerabilità rimanenti nel codice possano essere sfruttate.
- Architettura
- Least privilege Ogni processo deve essere eseguito con il minimo insieme di autorizzazioni necessarie. Accedere a un privilegio elevato deve essere possibile solo se indispensabile, e per il minor tempo possibile. Questo approccio riduce la possibilità che un utente o processo attaccante possa eseguire codice arbitrario altamente dannoso.
- Default deny Concedere permessi di accesso in base a condizioni e non a utenze. Ciò significa che, per impostazione predefinita, l’accesso è negato e il controllo identifica le condizioni in base alle quali è consentito l’accesso.
- Semplice e leggero Rendere il software il più semplice e leggero possibile: gli elementi complessi aumentano la probabilità di errori durante l’implementazione, configurazione, utilizzo, e mantenimento. Lo sforzo richiesto per raggiungere un livello adeguato di sicurezza aumenta notevolmente con la complessità dei meccanismi di sicurezza.
- Standard sicuri Adottare gli standard di codifica disponibili più sicuri, ponderando anche in base al contesto in cui deve operare.
- Programmazione
- Senza una lista esaustiva dei requisiti di sicurezza, lo sviluppatore avrà molta difficoltà ad individuare – in fase di programmazione – tutte le singole necessità. Si ricordi che lo sviluppatore necessita di concentrazione per la propria mansione, e aggiungere il carico di lavoro di designer e architetti può portare solamente a risultati scarsi.
- Linee guida di programmazione sicura In rete sono reperibili innumerevoli linee guida ed esempi che spiegano cosa è vietato scrivere nel codice, al fine di ottenere un software sicuro. Ad esempio, su Owasp.org è spiegato chiaramente per ogni linguaggio di programmazione come gestire il logout di un utente – “1. Invalidate user session (..). 2. Cancel cookie (..).” – con spiegazione del perché, di quali librerie utilizzare, e del codice da scrivere.
- Input validation È importantissimo convalidare tutti gli input con origine non attendibile: come le fonti di dati esterne, argomenti della riga di comando, interfacce di rete, variabili ambiente, e file ed input dell’utente. La corretta convalida dell’input può eliminare la maggior parte delle vulnerabilità del software. L’input non accettabile può essere respinto o ‘sanitizzato’, ovvero sostituendo o eliminando i caratteri del testo che possono essere pericolosi.
- Data sanitization Tutti i dati trasmessi a sottosistemi complessi per l’elaborazione (come shell di comandi, database, o applicativi), devono essere controllati e ripuliti. Un attaccante potrebbe, ad esempio, essere in grado di richiamare una funzione di questi componenti attraverso l’uso di ‘attacchi di injection’. Questo non è necessariamente un problema di validazione dell’input, anzi, in molti contesti, è il solo processo che effettua la richiesta ad avere la capacità di comprendere quali dati sia necessario inviare al sottosistema. In questo caso, il controllo deve porsi tra dati e richiedente, e trasmettere solo quanto necessario.
- Strumenti di analisi Scrivendo il codice, lo sviluppatore sarà assistito da avvisi generati in automatico dal compilatore, molto importanti per la correzione degli errori di programmazione. È generalmente di notevole aiuto utilizzare ulteriori strumenti di analisi statici e dinamici per rilevare ed eliminare molte problematiche di sicurezza comuni.
- Test
- Quality assurance I test per garantire la qualità del prodotto software necessitano di tecniche efficaci per individuare ed eliminare le vulnerabilità. I ‘fuzz test’, ‘penetration test’, e gli audit del codice sorgente dovrebbero essere incorporati come parte di un efficace programma di verifica e validazione. Revisioni e consulenze indipendenti possono operare con una prospettiva esterna, più efficacie per esempio sulla logica funzionale e nell’identificazione di ipotesi non valide.
- Strategie di mitigazione Utilizzare la ‘modellazione delle minacce’ al fine di gestire le possibili minacce. Questo processo comporta l’identificazione e la categorizzazione di tutto ciò che può essere minacciato e essere vettore – come le risorse interessate e i meccanismi di funzionamento del software. Per ciascun asset e componente devono essere individuate le possibili minacce, e classificate per rischio. L’attività di analisi deve portare allo sviluppo di strategie di mitigazione del rischio, all’implementazione e verifica delle contromisure.