Elaborato Perillo Pasquale N46001277
Transcript
Elaborato Perillo Pasquale N46001277
Scuola Politecnica e delle Scienze di Base Corso di Laurea in Ingegneria Informatica Elaborato finale in Sistemi Operativi Strumenti per l'analisi statica del software Anno Accademico 2014/2015 Candidato: Pasquale Perillo matr. N46001277 Alla mia famiglia e ai miei amici. Indice 1 Introduzione 1.1 Problema della sicurezza . . . . . . . . . . . . . . . . . . . . . 5 5 2 Metodi di analisi statica 11 2.1 Problemi di sicurezza comuni . . . . . . . . . . . . . . . . . . 12 2.2 Tipi di analisi . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 3 Tools di analisi statica 3.1 Flawfinder . . . . . . 3.2 Splint . . . . . . . . 3.3 Cppcheck . . . . . . 3.4 PMD . . . . . . . . . 3.5 FindBugs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 17 20 25 25 27 Conclusioni 31 Appendice. Codici Sorgente 33 Bibliografia 37 3 Capitolo 1 Introduzione Scopo di questo elaborato è quello di mostrare quanto sia importante analizzare dal punto di vista della sicurezza il software e i metodi di analisi statica del codice. Esistono diversi strumenti che servono a tale proposito, ne utilizzeremo quindi alcuni con degli esempi per evidenziarne le differenze e i diversi approcci. 1.1 Problema della sicurezza Negli ultimi anni l’ambito della sicurezza nel campo dell’informatica ha visto crescere la propria importanza, soprattutto grazie allo sviluppo della tecnologia, che ha portato alla formazione di sistemi informatici sempre più complessi e con componenti dislocati ed interconnessi tramite reti. Come riportato in [2], il National Institute of Standards and Technology definisce la computer security come: Computer Security. La protezione garantita ad un sistema informatico automatizzato allo scopo di raggiungere l’obiettivo di preservare l’integrità, la disponibilità e la confidenzialità delle risorse del sistema (include hardware, software, firmware, informazioni/dati e telecomunicazioni). I concetti di confidenzialità, integrità e disponibilità invece: Confidenzialità. Preservare le restrizioni sull’accesso e la divulgazione delle informazioni, inclusi i mezzi per proteggere la privacy personale e la proprietà delle informazioni. Una violazione della confidenzialità equivale ad una divulgazione non autorizzata di informazioni. Integrità. La protezione contro la modifica o la distruzione impropria dei dati, inclusa la garanzia del loro non disconoscimento e autenticità. Una vio5 6 1. INTRODUZIONE lazione dell’integrità equivale alla modifica o alla distruzione non autorizzata delle informazioni. Disponibilità. Garantire l’accesso tempestivo e affidabile alle informazioni. Una violazione della disponibilità equivale all’impossibilità di accedere o di utilizzare le informazioni di un sistema informatico. Naturalmente una componente chiave per la quale bisogna verificare la sicurezza è il software. Introdurre specifiche riguardanti la sicurezza nel ciclo di sviluppo è molto importante. Ad esempio in un sistema composto da diverse componenti che devono comunicare fra di loro è utile usare tecniche di cifratura dei messaggi, in modo che possano essere decodificate solo da chi è autorizzato; oppure se sono presenti dati sensibili ai quali non tutti gli utilizzatori possono accedere, si può implementare un processo di autenticazione per preservare la confidenzialità. Questo tipo di caratteristiche vengono implementate anche con meccanismi di protezione sofisticati: per le comunicazioni crittografate esistono algoritmi a chiave simmetrica come AES, a chiave asimmetrica come RSA oppure tecniche più recenti chiamate a crittografia quantistica; invece per autorizzare l’accesso alle informazioni è possibile utilizzare tecniche come il login tramite password oppure altri metodi come l’uso di identificatori biometrici. Tutto questo può non bastare, di solito infatti si tende a non considerare che anche aspetti di un programma non compresi nelle specifiche di sicurezza possono essere fonte di problemi. È necessario perciò ipotizzare un attaccante che tenta di forzare il sistema, usandolo anche in modi diversi dall’utilizzo tipico per il quale è stato pensato. Scrivere software sicuro significa che ogni sua parte deve essere in grado di comportarsi correttamente di fronte a qualsiasi tipo di attacco. Scovare falle nella sicurezza non è un lavoro facile. Per verificare che un sistema rispetti le sue specifiche il metodo più usato è quello di eseguirlo e testarne il comportamento. Avere un problema di sicurezza però non significa che il software non rispetti le sue caratteristiche, piuttosto che siano presenti delle “funzionalità” non desiderate le quali possono portare a comportamenti non sicuri. Individuare e gestire queste problematiche aiuta a rendere un programma solido e ci sono vari metodi che ci aiutano a farlo. Di seguito ne elenchiamo alcuni: • Un metodo usato è quello di simulare un attacco prima che il software venga rilasciato al pubblico. Di solito questi test sono di tipo black box, ovvero gli attaccanti non conoscono la struttura interna del sistema. Ci si può eventualmente avvalere di strumenti che mettono in atto attacchi già noti. Presumere che eventuali attaccanti non conoscano il programma all’interno non sempre è vero; una volta che il software è 1.1. PROBLEMA DELLA SICUREZZA 7 rilasciato un gruppo di persone con tempo a disposizione può testarlo approfonditamente, rivelandone molte caratteristiche. Questo è uno dei maggiori difetti del metodo black box; inoltre test che utilizzano sempre gli stessi tipi di attacchi non sono da ritenere significativi al fine di stabilire la vulnerabilità del programma. Nel caso in cui il software risulti vulnerabile anche a questi test significa che sono presenti problemi molto gravi. • Un altro approccio è quello denominato fuzzing, eseguendo il programma ponendogli ingressi casuali. Anche in questo caso i test possono essere di tipo black box ma ci sono anche implementazioni white box, ad esempio per determinare quali ingressi fanno eseguire determinate porzioni di codice. Per evitare il ripetersi di comportamenti simili, allo scopo di rendere i test più efficienti, gli strumenti tendono a diversificare gli input per coprire casi più variegati. Quando viene evidenziato un problema non sempre è immediato capirne l’origine, quindi insieme ai test possono essere usati altri analizzatori che si occupano di trovare errori specifici. Uno di questi è Valgrind, debugger automatizzato composto da una serie di tools che permettono di individuare errori di memoria, di cattiva gestione dei thread e altri, offrendo inoltre agli utenti la possibilità di scrivere propri analizzatori; altro esempio è AddressSanitizer, che si occupa di rilevare problemi inerenti la memoria dividendola in zone accessibili e non accessibili (come le regioni contigue a memoria allocata tramite ‘malloc’), riuscendo a trovare in questo modo errori come buffer overflow e accesso a memoria deallocata. Il fuzzing è un metodo molto dispendioso soprattutto in termini di tempo in quanto si rischia di ripetere molti test poco significativi a discapito di altri più utili. • Il metodo detto taint analysis è utilizzato per capire quali parti di un programma possono essere influenzate da un attaccante per forzare il sistema. In particolare si tratta di marchiare gli input (da cui ‘taint’) che possono arrivare da fonti non sicure e controllare, nel caso di test dinamici, i percorsi di esecuzione che vengono intrapresi dal programma e capire se portano a problemi di sicurezza. Questo tipo di analisi è molto utile per individuare validazione degli ingressi errata o assente ed in generale la solidità del comportamento del software. I metodi elencati sono detti dinamici, in quanto vengono utilizzati in una fase di sviluppo in cui il software è già funzionante e parzialmente completo. Inoltre, siccome analizzano il comportamento del programma durante l’esecuzione, non garantiscono una totale copertura. I prossimi invece si basano 8 1. INTRODUZIONE sull’analisi del codice in fase di scrittura. Molti bugs anche dal punto di vista della sicurezza possono essere individuati revisionando il codice senza usare un test che ne verifichi le proprietà. Scovare un problema nelle fasi iniziali di sviluppo non solo è meno costoso, ma rende più robusto il codice e migliora le fasi successive, concentrandosi ad esempio su test più specifici. Vediamone alcuni: • Il metodo di code review consiste proprio nell’analizzare il codice sorgente in cerca di difetti. Organizzare questo tipo di analisi in modo sistematico e con un gruppo di persone anche non coinvolte nella scrittura è molto utile. Chi è specializzato nella sicurezza può rilevare i problemi di scrittura che portano a creare falle comuni. Il difetto principale di questo approccio riguarda in particolare progetti di grandi dimensioni dove la revisione diventa molto complicata ed è indispensabile avere tante persone a disposizione e gestirle in modo adeguato per tenere conto di tutti gli aspetti. • Un altro metodo è l’analisi statica mediante l’uso di strumenti specifici. Uno strumento che analizza il codice senza eseguirlo sta effettuando un’analisi statica [1]. Revisionare il codice è molto utile ma, come abbiamo detto, si tratta di un lavoro difficile e dispendioso. Figura 1.1: Analisi statica 1.1. PROBLEMA DELLA SICUREZZA 9 Avvalersi di un tool snellisce questo processo e può aiutare proprio a scovare la fonte di problemi di sicurezza che emergono in qualsiasi fase di sviluppo. Lo svantaggio principale di questi tools è che non sempre sono precisi e perciò si tratta di un tipo di analisi che non si può ritenere completamente automatizzata. I risultati che forniscono vanno interpretati e non sempre evidenziano tutti i pericoli; non sostituiscono le persone ma rappresentano un valido aiuto per l’analisi del software. In Figura 1.1 [1] è rappresentato il processo di analisi appena esposto. Parleremo meglio di come si comportano e dei loro pregi e difetti nei prossimi capitoli. 10 1. INTRODUZIONE Capitolo 2 Metodi di analisi statica In questo capitolo ci concentreremo sull’analisi statica e sulla sua applicazione. Anche ai migliori programmatori capita di commettere errori o di ignorare alcune regole utili per evitare di creare vulnerabilità di sicurezza. Alcuni di questi problemi sono associati a delle regole di scrittura che possono essere verificate analizzando il codice sorgente, senza eseguire il software. I tools di analisi statica si propongono di applicare queste regole per ricercare diversi tipi di errori, fra cui falle di sicurezza. Con il loro ausilio è possibile individuare problemi comuni e altri più complessi; ogni volta che viene trovato un nuovo bug si possono formalizzare specifiche regole che gli analizzatori siano in grado di implementare. Inoltre, tolto l’onere di revisionare il codice manualmente, chi scrive può concentrarsi maggiormente nel risolvere i problemi piuttosto che ricercarli, tenendo presente che i risultati migliori si ottengono quando i programmi sono già scritti bene. Purtroppo questi tools non sono perfetti e i risultati che forniscono vanno anch’essi analizzati con cura. Infatti possono essere generati avvisi di un problema inesistente (falsi positivi) oppure che non venga segnalato un pericolo reale (falsi negativi). I falsi positivi sono difficili da gestire per chi deve analizzare i risultati di un analisi, in particolare se numerosi, in quanto bisogna verificarne la fondatezza per ognuno. Un buono strumento deve essere in grado di arginare questo aspetto senza però creare falsi negativi; è necessario perciò fare attenzione anche al caso in cui è presente un problema ma lo strumento usato non è in grado di rilevarlo. Se una porzione di codice non genera avvisi si tende a ritenerla sicura ma in realtà non è scontato. Una falla può non essere vista e rimanere latente anche per tanto tempo, risultando poi fatale, soprattutto nel campo della sicurezza. In base allo scopo dell’analisi svolta da un tool si può evincere la sua filosofia di bilanciamento fra falsi positivi e negativi. Strumenti che controllano la sicurezza, in particolare, devono assolutamente concentrarsi nel trovare più problemi possibili in modo da correggerli in 11 12 2. METODI DI ANALISI STATICA tempo e quindi possono generare un alto numero di falsi positivi. 2.1 Problemi di sicurezza comuni Prima di parlare nello specifico dei tools, analizziamo i problemi che devono cercare. Nel campo della sicurezza una falla non significa necessariamente che il programma non funziona o che non rispetta le specifiche, piuttosto che si comporta in modo inaspettato in presenza di comportamenti malevoli, mettendo in pericolo l’integrità e la segretezza di informazioni sensibili. Un programma sicuro deve quindi essere in grado di comportarsi in modo corretto di fronte a qualsiasi attacco. Trovare una parte di codice che un potenziale attaccante può sfruttare è vitale; per analizzarlo dobbiamo innanzitutto capire cosa si vuole cercare, conoscere i diversi tipi di errori, classificarli e studiare in che modo possono verificarsi. Le regole sulle quali si basa il funzionamento degli strumenti sono ricavate proprio da questi studi. Nel corso degli anni sono nati vari progetti che si pongono l’obiettivo di catalogare i problemi in modo da facilitare il compito dei tools e di chi analizza il software. Ad esempio OWASP (Open Web Application Security Project) è un’organizzazione non profit con l’obiettivo di fornire aiuto per migliorare la sicurezza del software, fornendo manuali e materiale gestito dalla comunità sul loro sito [5] in cui si definiscono concetti e metodi. Molto interessante è il progetto OWASP Top 10, che consiste in un documento che cataloga i problemi più grandi che possono capitare sviluppando applicazioni. Ogni voce di questa lista presenta una descrizione che ne indica la gravità e la diffusione, esempi pratici e metodi per capire se la vulnerabilità è presente e come prevenirla. Ne sono uscite due versioni, una nel 2010 e una nel 2013. Parleremo di alcune falle presenti nella lista più avanti. Il progetto CWE (Common Weakness Enumeration) consiste anch’esso nel fornire una lista di vulnerabilità, ricca di informazioni per permettere a chi analizza il software (compresi gli strumenti) di trovarle più facilmente. Il sito [6] gestito dalla comunità contiene centinaia di voci che comprendono un numero identificativo della debolezza, una descrizione, conseguenze in termini di violazioni di integrità, confidenzialità e disponibilità, metodi di ricerca nel codice, esempi pratici e altre informazioni. Molti tools basano le loro regole sulle indicazioni di questa lista ovvero ne fanno riferimento nei risultati che restituiscono. Alcuni esempi saranno illustrati nel capitolo successivo. Anche il NIST ha un proprio database, NVD (National Vulnerability Database). Si avvale di risorse gratuite come CWE ed in più fornisce un protocollo, SCAP (Security Content Automation Protocol), che usa queste componenti e definisce uno standard che regola il modo in cui vengono presentate le de- 2.1. PROBLEMI DI SICUREZZA COMUNI 13 bolezze. Inoltre cercando un determinato problema è possibile vedere quali programmi ne soffrono. Affrontiamo adesso nello specifico alcuni dei problemi più diffusi e potenzialmente molto dannosi per la sicurezza: • Il buffer overflow si verifica quando si tenta di scrivere in un buffer una quantità di dati maggiore della sua dimensione. Questo comporta la sovrascrittura della memoria adiacente che può contenere dati essenziali per l’esecuzione del programma. Oltre a comprometterne l’integrità, un attaccante può sfruttare questa vulnerabilità per eseguire proprio codice. Nel caso di mancato controllo sulla scrittura dello stack, i dati corrotti possono riempirlo fino ad arrivare ad esempio all’indirizzo di ritorno della funzione che si sta eseguendo, scrivendo un altro valore come l’indirizzo ad un’altra porzione di codice. In questo modo il programma eseguirà questo codice e avrà il controllo del sistema, arrivando anche a informazioni sensibili. Questo problema si verifica in linguaggi che lasciano al programmatore libertà di gestione della memoria come C e C++; è quindi necessario fare attenzione alle operazioni che il programma compie. Anche usare funzioni che notoriamente non svolgono nessun controllo sulla memoria può generare buffer overflow se non gestite in modo corretto. È presente nella lista CWE insieme a dei suoi sottotipi, come il già citato stack-based. • Gli attacchi SQL injection sono diffusi fra le applicazioni che usano un database e gli sottopongono query SQL. Se un sistema compone le richieste basandosi anche solo in parte su input esterni non controllati, possono essere inserite stringhe che sottoposte al database permettono operazioni non autorizzate. Questo è dovuto al fatto che il programma non conosce la semantica delle query SQL e gli input sono passati al database cosı̀ come arrivano. In questo modo un attaccante può usare il sistema con dei permessi che non gli sarebbero concessi e quindi accedere ad informazioni protette, distruggere dati sensibili, o modificare gli stessi permessi negando l’accesso a chi ne avrebbe diritto. Questo problema è uno dei più diffusi, in considerazione che molte applicazioni che agiscono sul web fanno uso di database, ma anche uno dei più rintracciabili attraverso l’analisi statica. È presente nella lista CWE ed è una delle voci principali della OWASP Top 10, compresa sia nella versione 2010 che 2013. • Il Cross-Site Scripting è una vulnerabilità che colpisce applicazioni web. Anche in questo caso riguarda la mancata gestione degli input, in particolare se usati dall’applicazione per generare pagine web. Un esempio 14 2. METODI DI ANALISI STATICA è un sito che attraverso una barra di ricerca permette agli utenti di cercare parole chiave presenti nello stesso. Un dato input può contenere pezzi di codice (script) che poi vengono eseguiti da utenti ignari, sottraendogli cosı̀ informazioni sensibili. Esempi di vulnerabilità di cross-site scripting sono lo stored, nel caso l’attaccante riesca a far memorizzare al server l’input malevolo che poi potrà essere aperto da altri client. Il reflected invece consiste nel far inviare dall’utente vittima una richiesta (tipo facendogli aprire un URL adibito allo scopo) ad un sito con il codice dell’attaccante; quando il server nella risposta restituisce anche ciò che ha inviato il client, lo script viene eseguito. Anche questo è un problema molto frequente e viene individuato efficacemente analizzando il codice. Sia il CWE che la OWASP Top 10 ne hanno una voce dedicata. Questi sono solo alcuni esempi di potenziali falle di sicurezza. Nel prossimo capitolo vedremo dei codici che ne sono affetti e come l’uso dei tools sia efficace per trovarli. 2.2 Tipi di analisi L’analisi statica viene quindi usata per scopi diversi e gli strumenti possono essere classificati a seconda dei problemi che vogliono identificare. In [1] vengono messi in evidenza a seconda delle mansioni che svolgono: • Controllare i tipi I “type checkers” si assicurano che non siano presenti errori nell’assegnazione dei tipi. Nonostante i compilatori dei linguaggi più diffusi effettuino già un controllo, i type checkers provano ad eliminare eventuali sviste che si possono tradurre in errori in fase di esecuzione. Soffrono di falsi positivi e di falsi negativi. • Controllare lo stile di programmazione Gli “style checkers” sono dei tools usati per controllare che il programma rispetti determinate regole di programmazione, come la struttura, le regole di denominazione di variabili e metodi, l’utilizzo di funzioni vulnerabili. Il difetto principale di questi analizzatori è la grande quantità di falsi positivi che possono produrre, in base al modo di scrivere codice da parte del programmatore. • Capire un programma I tools di “program understanding” aiutano a capire e ad esplorare meglio i codici. Quelli più complessi sono anche di valido ausilio per i 2.2. TIPI DI ANALISI 15 programmatori nella scrittura, rinominando automaticamente le variabili oppure dividendo una funzione in più metodi. Sono molto utili per operazioni di reverse-engineering; quelli più potenti provano anche ad estrapolare informazioni sulla progettazione analizzando il codice. • Verificare le proprietà di un programma I “property checkers” accettano delle proprietà in ingresso sotto forma di condizioni logiche che il programma deve rispettare. Un tipo di proprietà può essere quella di sicurezza temporale che imposta una serie concatenata di eventi nei quali il software non deve mai incorrere. Ad esempio non si deve leggere una locazione di memoria che è stata precedentemente liberata. • Trovare bug Un “bug finder” non si preoccupa tanto di come è scritto il codice ma piuttosto di scovare bug intesi come potenziali comportamenti non voluti del programma. Anche questi tools si basano su delle regole per trovare dei determinati patterns nel codice che spesso indicano la presenza di bugs. Di norma cercano di produrre un numero basso di falsi positivi, concentrandosi su patterns affidabili in grado di portare avvisi sempre utili, al costo di aumentare i falsi negativi. • Rispettare la sicurezza I “security reviewers” usano in genere le stesse tecniche delle altre tipologie di tools, ma vengono applicate per trovare falle di sicurezza nel software. Possono usare sia le caratteristiche degli style checkers, ad esempio cercando chiamate a funzioni non sicure, oppure i metodi dei property checkers, traducendo in proprietà certe regole di sicurezza e le modalità dei bug finders. I security reviewers soffrono di falsi positivi, in considerazione dell’importanza dell’analisi di ogni porzione di codice potenzialmente non sicura. 16 2. METODI DI ANALISI STATICA Capitolo 3 Tools di analisi statica In questo capitolo analizzeremo alcuni dei tools più diffusi fra quelli open source, evidenziando diversi approcci che ci permettano di trovare vari tipi di problemi. Inoltre attraverso alcuni esempi in C e Java verranno mostrate le caratteristiche di ogni strumento presentato, indicando attraverso i risultati vulnerabilità comuni nella pratica. I codici sono presenti in appendice. 3.1 Flawfinder Flawfinder è un security reviewer e supporta C e C++. Questo tool ha un database di funzioni potenzialmente pericolose che vengono ricercate nel codice sorgente del software. Ogni avviso è accompagnato da un livello di rischio che va da 0 (basso rischio) a 5 (alto rischio) in relazione non solo al metodo ma anche agli input con i quali lo stesso è stato invocato. Flawfinder è CWE compatibile, infatti se trova un metodo nella sua lista restituisce un avviso indicante anche una descrizione del problema che può generare ed il numero identificativo nella lista CWE della vulnerabilità. Alcuni dei metodi che il tool ricerca, se non gestiti e controllati in modo corretto, possono creare problematiche di buffer overflows (‘strcpy’,‘scanf’), uso di stringhe provenienti da fonti non fidate (‘printf’), mancata convalida degli input (sempre ‘scanf’,‘gets’), generando falle di sicurezza. Flawfinder, in modo analogo a strumenti come grep, si limita a comparare il proprio database con il codice e può generare avvisi anche solo nel caso dovesse trovare un metodo scritto da un utente recante lo stesso nome di uno presente nella propria lista. In ogni caso un avviso non implica necessariamente un problema e lo strumento non è in grado di cercare pezzi di codice potenzialmente pericolosi. Per questi motivi si verificano sia falsi positivi che falsi negativi. Però rispetto a grep i risultati possono rivelarsi molto utili 17 18 3. TOOLS DI ANALISI STATICA per le informazioni che forniscono, aiutando a focalizzarsi su quelle funzioni che notoriamente portano a comportamenti ambigui se non gestite nel modo corretto. Vediamo quale risultato riporta l’analisi del nostro codice d’esempio C con Flawfinder: Listing 3.1: Output Flawfinder Flawfinder version 1.31 , ( C ) 2001 -2014 David A . Wheeler . Number of rules ( primarily dangerous function names ) in C / C ++ ruleset : 169 Examining wrongcode . c FINAL RESULTS : wrongcode . c :10: [4] ( buffer ) scanf : The scanf () family ’ s % s operation , without a limit specification , permits buffer overflows ( CWE -120 , CWE -20) . Specify a limit to %s , or use a different input function . wrongcode . c :12: [4] ( buffer ) strcpy : Does not check for buffer overflows when copying to destination ( CWE -120) . Consider using strcpy_s , strncpy , or strlcpy ( warning , strncpy is easily misused ) . wrongcode . c :16: [4] ( format ) printf : If format strings can be influenced by an attacker , they can be exploited ( CWE -134) . Use a constant for the format specification . wrongcode . c :21: [4] ( buffer ) strcpy : Does not check for buffer overflows when copying to destination ( CWE -120) . Consider using strcpy_s , strncpy , or strlcpy ( warning , strncpy is easily misused ) . wrongcode . c :29: [3] ( buffer ) getenv : Environment variables are untrustable input if they can be set by an attacker . They can have any content and length , and the same variable can be set more than once ( CWE -807 , CWE -20) . Check environment variables carefully before using them . wrongcode . c :20: [2] ( buffer ) char : Statically - sized arrays can be improperly restricted , leading to potential overflows or other issues ( CWE -119: CWE -120) . Perform bounds checking , use functions that limit length , or ensure that the size is larger than the maximum possible length . ANALYSIS SUMMARY : Hits = 6 Lines analyzed = 44 in approximately 0.01 seconds (4531 lines / second ) Physical Source Lines of Code ( SLOC ) = 34 Hits@level = [0] 0 [1] 0 [2] 1 [3] 1 [4] 4 [5] 0 Hits@level + = [0+] 6 [1+] 6 [2+] 6 [3+] 5 [4+] 4 [5+] 0 3.1. FLAWFINDER 19 Hits / KSLOC@level + = [0+] 176.471 [1+] 176.471 [2+] 176.471 [3+] 147.059 [4+] 117.647 [5+] 0 Minimum risk level = 1 Not every hit is necessarily a security vulnerability . There may be other security vulnerabilities ; review your code ! Sono riportati 6 avvisi, 4 di livello 4, 1 di livello 3 e 1 di livello 2. L’analisi ha trovato dei metodi potenzialmente pericolosi e fornisce la riga in cui sono posizionati, la descrizione dei problemi che si possono presentare e l’id nella lista CWE. Notiamo che la descrizione della vulnerabilità è basata solo sulla conoscenza delle falle della funzione trovata e non su quello che può succedere nel codice specifico. Vediamo meglio questi avvisi: • Il primo avviso parla dell’uso di ‘scanf’ nel metodo ‘digitaStringa’. Quando viene usato l’identificatore ‘%s’, la dimensione dell’input ricevuto da ‘scanf’ non viene limitata, quindi è a forte rischio di buffer overflow. Per evitare che accada bisogna gestire in modo appropriato l’input, in particolare l’avviso ci suggerisce di limitare la dimensione della stringa accettata da ‘%s’ o di usare una funzione diversa. • Due avvisi riguardano l’uso di ‘strcpy’ nei metodi ‘digitaStringa’ e ‘accediIndice20’; anche in questi casi possono verificarsi buffer overflows. Infatti ‘strcpy’ non fa alcun controllo sui suoi parametri e quindi la destinazione può essere più piccola della stringa d’origine. La descrizione ci consiglia di usare funzioni alternative come ‘strncpy’, per la quale devono essere specificati quanti caratteri vogliamo copiare, indipendentemente dalla dimensione dell’origine. • È presente un avviso per l’uso di ‘printf’ nel metodo ‘stampa’. Siccome non vengono fatte assunzioni sulla stringa in ingresso, il tool fa notare che potrebbe provenire da fonti esterne, le quali se non verificate potrebbero essere sfruttate dagli attaccanti. • L’ultimo avviso riguarda il metodo ‘getenv’ che restituisce il valore di una variabile d’ambiente scelta. L’avviso precisa che non possiamo ritenere queste fonti fidate e vanno usate con cautela. Come abbiamo visto i risultati ottenuti non indicano necessariamente delle vulnerabilità, piuttosto parti di codice che vanno analizzate a fondo. Per tali motivi ci sono anche falsi negativi, problemi che con con questo tipo di analisi non possono essere trovati e li analizzeremo con gli altri tool. 20 3. TOOLS DI ANALISI STATICA 3.2 Splint Splint è un tool per C ed è basato sul controllo di regole che il codice deve rispettare. Integra funzionalità dei type e degli style checkers, dei bug finders ed aggiunge controlli sulla sicurezza. È capace di rilevare diversi tipi di problemi, fra i quali: • Dereferenziazione di puntatori NULL. • Valori indefiniti, ovvero quando una variabile viene usata prima di essere definita. • Uso errato dei tipi, aiutando i compilatori e inoltre si possono definire anche tipi astratti. • Gestione errata della memoria, come possibili accessi a memoria deallocata. • Buffer overflow. Presenta inoltre svariati flag per scegliere i tipi di avvisi da segnalare ed un meccanismo di annotazioni molto interessante. Per aggiungere un’annotazione basta indicare il simbolo ‘@’ all’inizio e alla fine di un commento contraddistinto dai marcatori ‘/*’ e ‘*/’; possono descrivere i parametri di input di una funzione, i valori di ritorno, variabili globali e altro ancora. Ad esempio, usando l’annotazione “/*@notnull@*/” per un parametro di un metodo, l’analisi dovrà considerare che quel valore non potrà essere nullo; in questo modo è possibile studiare diverse situazioni senza eseguire il codice. Le annotazioni servono anche per indicare precondizioni e postcondizioni di un metodo, in questo modo gli utenti possono indicare delle proprietà che il codice deve rispettare. Splint infatti verifica che partendo dalle precondizioni indicate si arrivi alle giuste postcondizioni. Oltre al controllo delle annotazioni aggiunge anche alcuni vincoli che si basano sulle sue regole interne. Ad esempio se viene dichiarato un array, aggiunge come postcondizione l’indice massimo col quale vi si può accedere, vincolo che deve essere rispettato (e quindi posto nella precondizione) quando il vettore viene utilizzato. Splint offre molte alternative ma si concentra soprattutto sull’analisi locale dei metodi e non sulle interazioni che possono avere fra loro. Inoltre, anche avendo varie possibilità di personalizzazione, il progetto ha subito un rallentamento nello sviluppo negli ultimi anni. Vediamo l’analisi che restituisce per il nostro codice: 3.2. SPLINT 21 Listing 3.2: Output Splint wrongcode . c : ( in function digitaStringa ) wrongcode . c :10:22: Unallocated storage tmp passed as out parameter to scanf : tmp An rvalue is used that may not be initialized to a value on some execution path . ( Use - usedef to inhibit warning ) wrongcode . c :12:2: Possible out - of - bounds store : strcpy ( str , tmp ) Unable to resolve constraint : requires maxSet ( str @ wrongcode . c :12:8) >= maxRead ( tmp @ wrongcode . c :12:12) needed to satisfy precondition : requires maxSet ( str @ wrongcode . c :12:8) >= maxRead ( tmp @ wrongcode . c :12:12) derived from strcpy precondition : requires maxSet ( < parameter 1 >) >= maxRead ( < parameter 2 >) A memory write may write to an address beyond the allocated buffer . ( Use - boundswrite to inhibit warning ) wrongcode . c : ( in function stampa ) wrongcode . c :16:2: Format string parameter to printf is not a compile time constant : str Format parameter is not known at compile - time . This can lead to security vulnerabilities because the arguments cannot be type checked . ( Use - formatconst to inhibit warning ) wrongcode . c : ( in function accediIndice20 ) wrongcode . c :23:2: Path with no return in function declared to return char There is a path through a function declared to return a value on which there is no return statement . This means the execution may fall through without returning a meaningful result to the caller . ( Use - noret to inhibit warning ) wrongcode . c :21:2: Possible out - of - bounds store : strcpy ( tmp , str ) Unable to resolve constraint : requires maxRead ( str @ wrongcode . c :21:13) <= 19 needed to satisfy precondition : requires maxSet ( tmp @ wrongcode . c :21:9) >= maxRead ( str @ wrongcode . c :21:13) derived from strcpy precondition : requires maxSet ( < parameter 1 >) >= maxRead ( < parameter 2 >) wrongcode . c :22:25: Possible out - of - bounds read : tmp [20] Unable to resolve constraint : requires maxRead ( str @ wrongcode . c :21:13) >= 20 needed to satisfy precondition : requires maxRead ( tmp @ wrongcode . c :22:25) >= 20 A memory read references memory beyond the allocated storage . ( Use - boundsread to inhibit warning ) wrongcode . c : ( in function main ) wrongcode . c :30:28: Possibly null storage home passed as non - null param : printf (... , home , ...) 22 3. TOOLS DI ANALISI STATICA A possibly null pointer is passed as a parameter corresponding to a formal parameter with no /* @null@ */ annotation . If NULL may be used for this parameter , add a /* @null@ */ annotation to the function parameter declaration . ( Use - nullpass to inhibit warning ) wrongcode . c :29:13: Storage home may become null wrongcode . c :32:12: Index of possibly null pointer c : c A possibly null pointer is dereferenced . Value is either the result of a function which may return null ( in which case , code should check it is not null ) , or a global , parameter or structure field declared with the null qualifier . ( Use - nullderef to inhibit warning ) wrongcode . c :28:17: Storage c may become null wrongcode . c :32:12: Array element c [0] used before definition wrongcode . c :36:34: Fresh storage c not released before return A memory leak has been detected . Storage allocated locally is not released before the last reference to it is lost . ( Use mustfreefresh to inhibit warning ) wrongcode . c :28:48: Fresh storage c created wrongcode . c :38:2: Return value ( type char ) ignored : accediIndice20 ( c ) Result returned by function call is not used . If this is intended , can cast result to ( void ) to eliminate message . ( Use - retvalother to inhibit warning ) wrongcode . c :32:12: Possible out - of - bounds read : c [0] Unable to resolve constraint : requires maxRead ( malloc (30 * sizeof ( char ) ) @ wrongcode . c :28:24) >= 0 needed to satisfy precondition : requires maxRead ( c @ wrongcode . c :32:12) >= 0 wrongcode . c :7:6: Function exported but not used outside wrongcode : digitaStringa A declaration is exported , but not used outside this module . Declaration can use static qualifier . ( Use - exportlocal to inhibit warning ) wrongcode . c :13:1: Definition of digitaStringa wrongcode . c :15:6: Function exported but not used outside wrongcode : stampa wrongcode . c :17:1: Definition of stampa wrongcode . c :19:6: Function exported but not used outside wrongcode : accediIndice20 wrongcode . c :23:1: Definition of accediIndice20 Finished checking --- 15 code warnings Splint genera 15 avvisi, più del doppio rispetto a Flawfinder. Questo evidenzia la maggiore varietà di problemi che è in grado di individuare, oltre alla filosofia di segnalare più situazioni possibili, a costo di produrre falsi positivi. In ‘digitaStringa’ abbiamo un avviso in più rispetto a Flawfinder e si tratta di un bug. Infatti la variabile ‘tmp’ viene dichiarata e poi usata senza esse- 3.2. SPLINT 23 re inizializzata. Compilatori come gcc non segnalano questo tipo di errori, molto comuni quando si scrive codice e difficili da individuare quando si incorre in problemi in fase di esecuzione. Grazie a questi strumenti diventa più semplice individuare e correggere queste distrazioni. Il secondo avviso, come in Flawfinder, riguarda l’uso di ‘strcpy’ e il potenziale buffer overflow, ma è interessante notare come Splint rileva il problema. Ad una variabile che deve essere scritta e con un certo numero di locazioni di memoria contigue allocate, viene assegnato un valore maxSet che rappresenta l’indice massimo al quale si può accedere tramite questa variabile. Ad esempio per ‘buf[BUFSIZE]’ abbiamo ‘maxSet = BU F SIZE − 1’. Stesso discorso vale per variabili che devono essere lette, a cui viene associato il valore dell’indice maxRead. Nel nostro caso quando viene invocata la funzione ‘strcpy(str,tmp)’ l’analisi verifica la precondizione ‘maxSet(str) ≥ maxRead(tmp)’, in caso contrario ‘tmp’ ha una dimensione maggiore di ‘str’ e si ha buffer overflow. In ‘digitaStringa’ Splint non è in grado di sapere la dimensione dell’ingresso ‘str’ e della stringa ‘tmp’ (perché abbiamo visto che non viene allocata) perciò è una potenziale falla, ma con questo metodo è possibile trovare casi in cui il buffer overflow si verifica sicuramente o in cui non si verifica affatto, evitando sia falsi negativi che falsi positivi. Nella funzione ‘stampa’ viene segnalato che ‘printf’ ha in ingresso una stringa non costante in fase di compilazione, quindi se non convalidata nel modo giusto può essere una vulnerabilità. In ‘accediIndice20’ Splint ci fa notare che deve restituire un valore char ma che non sempre accade; se durante un esecuzione la condizione ‘tmp!=NULL’ non è verificata, il problema si presenta. Oltre ad un nuovo avviso di possibile buffer overflow per strcpy, viene fatto notare che in ‘return tmp[20]’ può esserci un problema di lettura out of bounds (fuori dai limiti), ovvero si tenta di leggere un indice della stringa non allocato. Siccome la dimensione di ‘tmp’ è 20, il problema si presenta sicuramente. Per il ‘main’ sono generati i seguenti avvisi: • La stringa ‘home’ posta in ingresso a ‘printf’ potrebbe essere nulla, infatti ‘getenv’ in caso di errore restituisce valore nullo e la stringa non viene controllata. • Il secondo avviso riporta che quando viene invocata la ‘malloc’ può restituire ‘NULL’ ma non è verificato. • La variabile ‘c’ nonostante venga allocata, viene usata (tentando la lettura di ‘c[0]’ nella condizione del ciclo ‘while’) senza che venga definita prima. 24 3. TOOLS DI ANALISI STATICA • In uno dei potenziali percorsi di esecuzione ‘c’ non viene deallocata, infatti se la condizione “ c[0]==‘b’ ” è verificata, il programma smette di eseguire senza compiere l’operazione. Anche questi sono errori comuni che sfuggono ai compilatori, ma possono portare problemi di memory leak oppure di lettura out of bounds. • Quando viene usata la funzione ‘accediIndice20’ il valore che restituisce viene ignorato. • Gli ultimi due risultati riguardano lo stile di scrittura del codice, suggerendo di dichiarare i metodi ‘digitaStringa’ e ‘stampa’ come ‘static’ considerato che non vengono usate al di fuori del nostro programma. Vediamo adesso un esempio di uso delle annotazioni. Siccome abbiamo individuato che la funzione ‘accediIndice20’ ha un problema di lettura out of bounds, potremmo modificarla in questo modo: char accediIndice20 ( char * str ) { if ( str != NULL ) return str [20]; } Siccome l’ingresso non viene più copiato in una stringa di dimensioni 20 come avveniva precedentemente con ‘tmp’, nel caso ‘str’ sia abbastanza grande il problema non si presenta più. Tuttavia questa implementazione non fa alcun controllo sulla dimensione della stringa che accetta in ingresso, quindi a seconda della sua grandezza il problema può presentarsi di nuovo. Possiamo fare in modo che Splint controlli l’input ogni volta che viene invocato il metodo cosı̀ da generare un avviso se non ha una grandezza adeguata. Per questo possiamo aggiungere un vincolo alle precondizioni della funzione, usando il meccanismo citato precedentemente, sul valore ‘maxRead’ (che nel nostro caso deve essere almeno 20) tramite la clausola requires. La dichiarazione di ‘accediIndice20’ diventa: char accediIndice20(char* str)/*@requires maxRead(str)>=20 @*/ Splint riporta il seguente avviso quando la funzione viene usata nel ‘main’: wrongcode . c :38:2: Possible out - of - bounds read : accediIndice20 ( c ) Unable to resolve constraint : requires maxRead ( malloc (30 * sizeof ( char ) ) @ wrongcode2 . c :28:20) >= 20 needed to satisfy precondition : requires maxRead ( c @ wrongcode2 . c :38:17) >= 20 derived from accediIndice20 precondition : requires maxRead ( < parameter 1 >) >= 20 A memory read references memory beyond the allocated storage . ( Use boundsread to inhibit warning ) 3.3. CPPCHECK 25 Nonostante non sia in grado di calcolare ‘maxRead’ per array allocati con ‘malloc’ e quindi non può verificare direttamente se il vincolo è rispettato, evidenzia che la variabile ‘c’ deve poter essere acceduta almeno all’indice 20 per usare la funzione senza problemi. 3.3 Cppcheck Cppcheck è un bug finder per C e C++. Si basa su regole e patterns per controllare metodi, tipi e variabili. La filosofia di questo tool è quella di limitare i falsi positivi, quindi di generare avvisi solo in presenza di effettivi errori. Può individuare buffer overflows, memory leaks, dereferenziazione di puntatori NULL, variabili non inizializzate, usi errati delle funzioni della STL, l’uso di funzioni non sicure o obsolete, codice ridondante ed altro. Nonostante non sia orientato specificatamente per la sicurezza, può anche individuare problemi di quel genere. Inoltre si possono creare regole personalizzate per affinare l’analisi. Vediamo come si comporta analizzando il nostro codice: Listing 3.3: Output Cppcheck Checking wrongcode . c ... [ wrongcode . c :22]: ( error ) Array ’ tmp [20] ’ accessed at index 20 , which is out of bounds . [ wrongcode . c :10]: ( error ) Uninitialized variable : tmp [ wrongcode . c :32]: ( error ) Memory is allocated but not initialized : c L’analisi restituisce solo 3 avvisi, un numero inferiore sia di Flawfinder che di Splint. Comunque possiamo notare che i risultati ottenuti corrispondono tutti a veri e propri bug del codice, sia il tentativo di lettura di ‘c[0]’ senza che sia definito nel main, sia la non inizializzazione della variabile ‘tmp’ in ‘digitaStringa’, che la lettura out of bounds di ‘tmp[20]’ in ‘accediIndice20’. Dall’analisi con questo tool non vengono fuori tutti gli errori presenti nel programma. La scelta di limitare i falsi positivi ed un’analisi non sempre precisa possono portare a molti falsi negativi. In ogni caso è un tool che attualmente viene ancora sviluppato e migliorato. 3.4 PMD PMD è uno style checker per Java, JavaScript, PLSQL, Apache Velocity, XML, XSL. Include un add-on, CPD (Copy paste detector) che identifica duplicazioni di codice e supporta anche altri linguaggi come C, C++, C# e PHP. PMD analizza il codice sorgente e verifica che rispetti un insieme di 26 3. TOOLS DI ANALISI STATICA regole. Queste regole sono divise in categorie e quando si effettua un’analisi si può scegliere quali usare in modo da concentrarci solo sugli aspetti desiderati. Elenchiamo di seguito alcune categorie: • Basic, comprende regole di buona scrittura del codice. Alcune sono: “ForLoopShouldBeWhileLoop”, cerca cicli ‘for’ che potrebbero essere scritti come cicli ‘while’; “UnconditionalIfStatement”, consiglia di non usare ‘if’ con condizioni sempre vere o sempre false; “CheckResultSet”, avvisa se è stato controllato il valore di ritorno di metodi di navigazione come ‘next’ o ‘last’. • Unusued Code, le regole di questa categoria cercano codice che non viene usato o che non serve, come variabili o metodi solo dichiarati (“UnusedLocalVariable” e “UnusedPrivateMethod”). • Empty Code, serve per cercare blocchi di codice vuoti, come if (EmptyIfStmt), while (EmptyWhileStmt), try (EmptyTryBlock). • String and StringBuffer, sono regole che evitano usi problematici delle classi come ‘String’ e ‘StringBuffer’. Esempi sono: “StringToString”, cerca se viene invocato il metodo ‘toString’ su oggetti che sono già String; “UseEqualsToCompareStrings”, suggerisce di usare il metodo equals invece degli operatori ‘==’ e ‘!=’; “UseStringBufferLength”, consiglia l’uso del metodo ‘length’ di StringBuffer piuttosto che alternative come ‘StringBuffer.toString().length()’. • Sun Security, nonostante sia presente esplicitamente questa categoria che riguarda la sicurezza (si basa su una guida di Oracle [14] per scrivere codice sicuro), è molto scarna e presenta solo due regole al suo interno: “MethodReturnsInternalArray”, indica se una funzione usa come valore di ritorno un array della classe violandone l’incapsulamento. Infatti il riferimento può essere usato sia per modificare che per cancellare l’array (la regola suggerisce di restituire una copia); “ArrayIsStoredDirectly”, segnala se metodi della classe memorizzano un array in una variabile interna. Il riferimento che viene memorizzato è posseduto anche da chi ha modificato l’array e quindi può essere usato senza passare per le funzioni della classe (violando di nuovo l’incapsulamento). Anche in questo caso è suggerito di memorizzare una copia. PMD non è pensato per la sicurezza ma ha il vantaggio di permettere di scrivere bene codice e aggiungere proprie regole. L’azienda informatica Gotham Digital Science (GDS), ad esempio, ha scritto un set di regole incentrato nel trovare problemi di sicurezza [15], nello specifico Injection, Cross-Site 3.5. FINDBUGS 27 Scripting, Insecure Cryptographic Storage, Failure to Restrict URL Access e Unvalidated Redirects and Forwards, tutte vulnerabilità elencate nella OWASP Top 10. Vediamo l’analisi del nostro codice Java, selezionando le regole basic, unusedcode, strings, sunsecure e il plugin di GDS: Listing 3.4: Output PMD Wrong . java :33 Avoid empty catch blocks Wrong . java :39 Returning ’ online ’ may expose an internal array . Wrong . java :42 Avoid unused private methods such as ’ eseguiGet ( HttpServletRequest , H t t pS e r vl e t R es p o ns e ) ’. Wrong . java :22 username of type java . lang . String concatenated to SQL string creating a possible SQL Injection vulnerability . Additional information : username appears to be a method argument . La maggior parte dei risultati riguardano regole di buona scrittura del codice che non portano necessariamente a problemi. Il primo avviso ci indica che è presente un blocco catch vuoto (nel metodo ‘login’); il terzo che il metodo privato ‘eseguiGet’ non viene mai usato all’interno della classe. In progetti di grandi dimensioni risultati di questo tipo possono essere tanti ma la possibilità di scegliere solo un sottoinsieme di regole aiuta a mitigare questo aspetto ed a concentrarsi su quello che serve veramente. Notiamo inoltre che i restanti due avvisi riguardano la sicurezza. Uno riguarda la funzione ‘utenti online’ dato che restituisce come valore un array privato che può essere esposto anche a chi non dovrebbe vederlo. Il secondo è stato trovato grazie al plugin sulla sicurezza di GDS ed individua il problema di SQL injection nel metodo ‘login’. Altro limite di PMD è che analizza un solo file per volta, perciò eventuali problemi dovuti ad una correlazione fra più file non vengono evidenziati. Per questo e altri motivi l’analisi non sempre è precisa, soffre di falsi negativi e nonostante ci sia una regola per vulnerabilità di cross-site scripting, quella presente nel metodo ‘eseguiGet’ non viene individuata. 3.5 FindBugs FindBugs è, come suggerisce il nome, un bug finder e supporta Java. Questo tool analizza il bytecode del programma Java cercando bug patterns, caratteristica molto utile in quanto non c’è bisogno di avere il codice sorgente. Findbugs comprende diverse categorie di patterns, tra cui: • Bad Practice, raccoglie errori comuni e regole di buona scrittura del codice, in modo simile a quanto fanno gli style checkers. • Malicious code vulnerability, cerca codice che può essere sfruttato da un attaccante. Di solito si tratta di variabili o metodi che non hanno prote- 28 3. TOOLS DI ANALISI STATICA zione adeguata, ad esempio il pattern “MS FINAL PKGPROTECT” cerca campi ‘static’ e modificabili consigliando di dichiararli ‘final’ e ‘protected’ in modo che non possano essere modificati da altri package. • Security, FindBugs ha una categoria dedicata alla sicurezza. Sono presenti regole per ricercare vulnerabilità come SQL injection, Cross-site scripting, connessioni a database non sicure ed altro. Findbugs quindi comprende anche una categoria specifica per la sicurezza e supporta un’architettura a plugin che permette di aggiungere nuovi patterns. In particolare molto interessante è il plugin Find Security Bugs [17] che aumenta la capacità del tool di trovare problemi legati alla sicurezza. Aggiunge molti patterns alla categoria security, facendo riferimento alle vulnerabilità sia della OWASP Top 10 che di CWE. Come possiamo vedere in Figura 3.1 il tool presenta un’interfaccia grafica che ci permette di navigare meglio fra i risultati consentendo anche diversi tipi di raggruppamento e assegna ad ognuno di loro una certa gravità rappresentata da un pallino colorato: viola è un livello basso, giallo è medio e rosso è alto. Selezionando uno degli errori individuati troviamo in basso a sinistra informazioni come il nome dell’errore, la riga, il metodo in cui si trova ed eventualmente altri dettagli. In basso a destra invece è presente un riquadro con una breve descrizione dell’errore e in alcuni casi il riferimento a liste come CWE e OWASP Top 10. Figura 3.1: Interfaccia Findbugs 3.5. FINDBUGS 29 Figura 3.2: Risultati Findbugs Analizzando il codice di prova vengono restituiti 13 avvisi di cui 6 di sicurezza. In Figura 3.2 mostriamo i risultati delle altre categorie mentre in Figura 3.3 quelli di sicurezza. L’analisi di Findbugs è molto dettagliata ed offre tanti spunti per correzioni di parti di codice che possono portare potenzialmente a problemi. Nella categoria Correctness ad esempio ci viene segnalato che nel metodo ‘login’ tentiamo di scrivere un campo (‘online’) che potrebbe non essere stato inizializzato, infatti non è presente il costruttore della classe che dovrebbe occuparsene. I due avvisi nella categoria Bad Practice ci fanno notare che sempre nel metodo ‘login’ le variabili di tipo ‘Connection’ e ‘Statement’ non vengono chiuse, cosa che non comporta necessariamente errori ma sarebbe meglio svolgere. Per quanto riguarda la sicurezza possiamo notare subito che sono individuati sia il problema di SQL injection in ‘login’ che quello di cross-site scripting in ‘eseguiGet’. Inoltre altri due avvisi ci evidenziano la pericolosità di trattare password costanti direttamente nel codice sorgente come avviene invocando ‘getConnection’ per la variabile ‘conn’. Infatti qualunque persona che possa accedere al codice o al bytecode può leggere questa password, in particolare la descrizione consiglia di memorizzarle in file di configurazione esterni. Possiamo notare come il codice sia revisionato da Findbugs sotto diversi aspetti e una porzione può generare anche più di un risultato. In particolare l’uso di ‘executeQuery’ non restituisce solo l’avviso di potenziale SQL injection ma anche un altro più 30 3. TOOLS DI ANALISI STATICA Figura 3.3: Risultati sicurezza Findbugs generico di Potential SQL Problem dovuto alla pericolosità di usare stringhe non costanti in questi contesti, soprattutto se non controllate. Lo stesso vale per l’uso di ‘getParameter’ nel metodo ‘eseguiGet’ in quanto Findbugs ci dice che non solo può essere un problema di cross-site scripting ma anche che queste funzioni restituiscono valori da fonti potenzialmente non sicure. Nonostante l’ampiezza del database che consente di trovare molti problemi comuni, l’analisi può rivelarsi imprecisa e produrre sia falsi positivi che falsi negativi. Secondo quanto riporta il sito del tool [16] il rate di falsi positivi è inferiore del 50% rispetto agli avvisi totali. Conclusioni Includere nello sviluppo fasi dedicate alla ricerca di problemi di sicurezza è fondamentale per scrivere software solido, in grado di proteggere informazioni sensibili e fronteggiare potenziali attacchi in ogni situazione. L’analisi statica aiuta a ricercare bugs e problemi di sicurezza, a trovare errori comuni consentendo la diminuzione dei costi e migliora la qualità del codice. La sua efficacia aumenta se usata col supporto di altri tipi di analisi, come ad esempio i test dinamici. Quando viene individuato un problema i tools statici aiutano a capire la struttura del codice e a leggerlo meglio, ricercando quelle porzioni che possono portare a comportamenti ambigui. Tuttavia non è possibile trovare tutti gli errori e gli strumenti utilizzati non sono sempre precisi. È utile perciò usare più di un tool in quanto ogni strumento applica un diverso approccio all’analisi ed è in grado di trovare alcuni tipi di vulnerabilità, ignorandone altri. Utilizzarli tutti insieme consente di sfruttare le loro qualità peculiari, compensando i difetti di ognuno. Per questi motivi i programmatori devono fare molta attenzione nella scrittura del codice, non confidando troppo nelle analisi successive. Solo in questo modo il lavoro dei tools diventa veramente produttivo, il numero di avvisi diminuisce ed è possibile arrivare all’individuazione di problemi fino a quel punto ignorati. 31 32 CONCLUSIONI Appendice. Codici Sorgente Sono presenti di seguito i codici analizzati dai tools in esame. Il codice C è composto da tre metodi: ‘digitaStringa’, copia una stringa inserita tramite tastiera in un buffer che accetta come input; ‘stampa’, stampa la stringa che riceve come ingresso; ‘accediIndice20’, restituisce il ventesimo carattere di un array di char. Inoltre è presente un main che usa queste funzioni svolgendo piccole operazioni. Listing: Codice C 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 # include < stdio .h > # include < stdlib .h > # include < string .h > # define BUFSIZE 30 void digitaStringa ( char * str ) { char * tmp ; int succ = scanf ( " % s " , tmp ) ; if ( succ != 0) strcpy ( str , tmp ) ; } void stampa ( char * str ) { printf ( str ) ; } char accediIndice20 ( char * str ) { char tmp [20]; strcpy ( tmp , str ) ; if ( tmp != NULL ) return tmp [20]; } 33 34 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 APPENDICE. CODICI SORGENTE int main () { char * c =( char *) malloc ( BUFSIZE * sizeof ( char ) ) ; char * home = getenv ( " HOME " ) ; printf ( " Ti trovi in % s \ n " , home ) ; while ( c [0] != ’a ’) { if ( c [0] == ’b ’) return 0; digitaStringa ( c ) ; accediIndice20 ( c ) ; stampa ( c ) ; } free ( c ) ; return 0; } Il codice Java definisce una classe ‘Wrong’ con due variabili private e 3 metodi, uno pubblico, uno protetto e uno privato. La funzione pubblica ‘login’ permette all’utilizzatore di accedere ad un database e soffre di SQL injection. La funzione privata ‘eseguiGet’ è presa da un file di test del GDS e presenta una vulnerabilità di cross-site scripting di tipo reflected. L’array di stringhe ‘online’ contiene gli username di tutti gli utenti che hanno effettuato il login, ‘num online’ il numero di questi ultimi. Infine il metodo protetto ‘utenti online’ restituisce la variabile ‘online’. Listing: Codice Java 1 2 3 4 5 6 7 8 9 10 11 12 13 package wrong ; import java . sql .*; import java . util .*; import java . io . IOException ; import java . io . PrintWriter ; import import import import import javax . servlet . ServletException ; javax . servlet . http . HttpServlet ; javax . servlet . http . H tt pS er vle tR eq ue st ; javax . servlet . http . H t t pS e r vl e t R es p o ns e ; javax . servlet . http . HttpSession ; public class Wrong { 35 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 private String [] online ; private int num_online ; public void login ( String username , String password ) { try { String url = " " ; Connection conn = DriverManager . getConnection ( url , " username " ," password " ) ; String query = " select * from user where username = ’ " + username + " ’ and password = ’ " + password + " ’" ; Statement stmt = conn . createStatement () ; ResultSet rs = stmt . executeQuery ( query ) ; if ( rs . next () ) { System . out . println ( " Successfully logged in " ) ; this . online [ this . num_online ++]= username ; } else { System . out . println ( " Username and / or password not recognized " ) ; } } catch ( SQLException e ) { } } protected String [] utenti_online () { return online ; } private void eseguiGet ( H ttp Se rv le tR eq ue st request , H tt p S e rv l e tR e s po n s e response ) throws ServletException , IOException { PrintWriter pw = response . getWriter () ; String v1 = request . getParameter ( " p1 " ) ; String w1 = v1 ; pw . print ( w1 ) ; } } Bibliografia [1] Brian Chess, Jacob West, Secure Programming with Static Analysis, 2007 [2] William Stallings, Operating Systems: Internals and Design Principles (6th Edition), 2008 [3] Valgrind, http://valgrind.org/ [4] AddressSanitizer, https://github.com/google/sanitizers/wiki/AddressSanitizer [5] https://www.owasp.org/index.php/Main Page [6] https://cwe.mitre.org/index.html [7] https://nvd.nist.gov/ [8] David Waltermire, Stephen Quinn, Karen Scarfone, Adam Halbardier, The Technical Specification for the Security Content Automation Protocol(SCAP): SCAP Version 1.2, 2011 [9] Flawfinder, http://www.dwheeler.com/flawfinder/ [10] Splint Manual, http://www.splint.org/manual/ [11] David Evans, David Larochelle, Improving Security Using Extensible Lightweight Static Analysis, 2002 [12] Cppcheck - A tool for static C/C++ code analysis, https://sourceforge.net/p/cppcheck/wiki/Home/ [13] PMD, https://pmd.github.io/pmd-5.4.1/index.html [14] Secure Coding Guidelines for Java SE , http://www.oracle.com/technetwork/java/seccodeguide-139067.html#gcg, 2014 37 38 BIBLIOGRAFIA [15] Justin Clarke, Securing Development with PMD - Teaching an Old Dog New Tricks, https://www.owasp.org/images/c/cc/Securing Development with PMD Teaching an Old Dog New Tricks - OWASP.pdf, 2013 [16] FindBugs Fact Sheet, http://findbugs.sourceforge.net/factSheet.html [17] Find Security Bugs, http://find-sec-bugs.github.io/