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/