Quasi tutto il software chiama più livelli di librerie di terze parti. Supponiamo, per esempio, che un programma Java invochi una funzione da una libreria standard per formattare una data. Quella funzione potrebbe a sua volta chiamare una funzione da un’altra libreria che capisce il calendario. E quella funzione ne chiama un’altra, e così via.
E se una falla di sicurezza in una di queste librerie profondamente annidate viene resa pubblica? Il vostro programma è ora a rischio di compromissione, e un malintenzionato può entrare nel server su cui il vostro programma è in esecuzione–anche se non avete introdotto un vostro bug.
Ci sono molti scanner che vi aiutano a trovare le vulnerabilità nelle dipendenze, ma la loro gestione comporta alcune sottigliezze. Vedremo il processo in questo articolo.
Fonti di informazioni sulle falle
Tutti noi siamo protetti da una lontana rete di esperti di sicurezza che sottopongono il software a tutti i tipi di test tortuosi per rivelare falle pericolose, e riportano queste falle agli sviluppatori. I loro test possono essere semplici come lanciare input insoliti ad una funzione per vedere se la funzione si confonde e permette ad un intruso di prendere il controllo del programma. Un’interessante disciplina chiamata “fuzzing” sottopone grandi quantità di caratteri generati casualmente ai programmi, ed è sorprendentemente efficace nel trovare bug e vulnerabilità. Ci sono anche strumenti di analisi completi che cercano problemi sospetti nel codice stand-alone (analisi statica) o in un programma in esecuzione (analisi dinamica).
Naturalmente, anche i ricercatori meno ben intenzionati sono alla ricerca di tali falle, con l’obiettivo di creare exploit malevoli per i clienti dei governi e gruppi transomware. Anche se le falle di sicurezza non ancora note al pubblico (zero day exploit) sono pericolose, la maggior parte degli attacchi utilizza falle che sono pubblicamente note, e che le vittime hanno permesso di rimanere sui loro sistemi. Siate certi che gli attori malintenzionati stanno leggendo le liste pubbliche delle falle.
Le falle pubblicamente note sono pubblicate su database mantenuti da organizzazioni di sicurezza, in particolare il database CVE (Common Vulnerabilities and Exposures). Il National Institute of Standards and Technology (NIST), una delle principali agenzie governative statunitensi per gli standard nel software e altrove, mantiene il National Vulnerability Database, che aggiunge più dettagli agli exploit nel database CVE. Un altro sforzo recente per raccogliere le falle conosciute nelle librerie di software libero è il database Open Source Vulnerabilities (OSV). E alcuni progetti offrono raccolte specifiche di vulnerabilità rilevanti, come il Python Packaging Advisory Database.
Non dovete leggere ossessivamente la sempre crescente lista di vulnerabilità; ne vengono scoperte così tante ogni giorno che non potreste nemmeno stare al passo con loro. Per ogni linguaggio di programmazione popolare, è possibile eseguire uno strumento per cercare automaticamente le liste e dirvi cosa è stato scoperto per tutte le librerie che la vostra applicazione utilizza. Vedere il sito di GitLab per una lista di strumenti automatici. GitHub offre anche controlli automatici attraverso un servizio chiamato Dependabot.
È conveniente usare gli strumenti offerti da GitLab e GitHub perché, con pochi click, puoi far eseguire il controllo nei punti chiave del tuo ciclo di sviluppo. Ma non è necessario essere su GitLab o GitHub per eseguire gli strumenti. Puoi integrarli manualmente nel tuo ciclo di sviluppo. I programmi Java e .NET possono anche usare lo strumento OWASP Dependency-Check.
Quando dovreste eseguire uno strumento? Se potete tollerare il tempo che aggiunge ad un check-in, vi suggerisco di eseguirlo su ogni check-in che potete. In primo luogo, potreste aver aggiunto un nuovo pacchetto alla vostra applicazione durante le vostre ultime modifiche, e se il pacchetto è difettoso, vorreste saperlo subito in modo da poter prendere i passi elencati in questo articolo per risolvere il problema. In secondo luogo, le nuove vulnerabilità vengono scoperte così spesso che vi capiterà regolarmente di scoprire un nuovo problema in un pacchetto che prima andava bene.
Come minimo, eseguite un controllo automatico delle vulnerabilità prima di un passo importante nel ciclo di vita, come il controllo della qualità o il deployment. Non volete entrare in una fase importante del ciclo di vita con una vulnerabilità, perché correggerla diventa molto più costoso.
L’esecuzione di scanner di vulnerabilità su base regolare è una parte centrale di DevSecOps, una pratica di tendenza che integra la sicurezza nel ciclo di vita dell’applicazione. Alcuni ambienti normativi, tra cui sia la CIA che l’FBI, richiedono scansioni che seguono il Security Content Automation Protocol (SCAP). SCAP è stato sviluppato dal NIST e ha un’implementazione open source chiamata Open SCAP.
Facili correzioni
Hai scoperto una vulnerabilità! Speriamo che la correzione sia rapida e indolore. Se gli sviluppatori del pacchetto hanno rilasciato una nuova versione con la correzione, tutto quello che dovete fare è ricostruire la vostra applicazione usando la versione corretta. Naturalmente, ogni modifica ad un pacchetto introduce potenzialmente nuovi problemi, quindi è necessario eseguire i test di regressione dopo l’aggiornamento.
Una tendenza sofisticata nella costruzione del software è rappresentata da Project Thoth, uno strumento open source sviluppato da Red Hat per trovare librerie sicure per applicazioni Python. (Thoth non si limita ad estrarre l’ultima versione stabile di ogni libreria usata dall’applicazione; consulta vari database pubblici e cerca di raccomandare una combinazione di pacchetti che funzionino insieme senza difetti. Lo stesso approccio è stato copiato dagli sviluppatori di altri linguaggi di programmazione.
Se non c’è ancora una nuova versione che ha risolto la vulnerabilità del software, forse potete trovare una vecchia versione della libreria che non contiene la vulnerabilità. Naturalmente, se la vecchia versione ha altre vulnerabilità, non vi aiuterà molto. E se la vecchia versione sembra soddisfare le vostre esigenze, dovete assicurarvi di non dipendere dalle caratteristiche aggiunte alle nuove versioni, e di nuovo dovete eseguire i vostri test di regressione.
Determinare la portata di un difetto
Supponiamo che le soluzioni suggerite nella sezione precedente non siano disponibili. Siete bloccati a costruire il vostro programma con una libreria che ha una falla di sicurezza identificata. Ora sono necessarie alcune ricerche e ragionamenti sottili.
Guardate le circostanze che possono innescare una violazione. Molti exploit sono teorici quando i ricercatori di sicurezza li segnalano, ma possono diventare rapidamente reali. Quindi leggete il rapporto di vulnerabilità per vedere i requisiti che un attaccante deve avere per diventare un rischio. Hanno bisogno di un accesso fisico al vostro sistema? Hanno bisogno di essere superuser (root)? Una volta che diventano root, probabilmente non hanno bisogno di sfruttare la vostra falla per creare il caos. Potreste decidere che è improbabile che un attaccante sia in grado di eseguire l’exploit nel vostro particolare ambiente.
Alcuni scanner automatici di vulnerabilità sono apertamente sensibili. Potrebbero segnalare qualcosa come un problema, ma voi potreste decidere che non è un problema nel vostro caso.
Potreste anche essere in grado di inserire più controlli per garantire che la falla non venga sfruttata. Supponiamo che un argomento passato alla funzione vulnerabile sia la lunghezza di un buffer, e che l’exploit sia un rischio solo se tale argomento è negativo. Naturalmente, la lunghezza di un buffer dovrebbe essere sempre zero o positiva. Il vostro programma non chiamerà mai legittimamente la funzione con un valore negativo in quell’argomento. Potete rafforzare la sicurezza aggiungendo questo prima di ogni chiamata alla funzione:
if (argument < 0)
, exit;
Altri exploit funzionano iniettando caratteri che non dovrebbero mai essere usati in input legittimi, quindi potete controllare questi caratteri prima di passare input alle funzioni. Alcuni linguaggi, seguendo un'innovazione introdotta molti anni fa in Perl, marcano le variabili a rischio come "contaminate" in modo da sapere che le avete controllate per violazioni della sicurezza.
Potrebbe essere più facile aggiungere un controllo per l'input pericoloso in un proxy dell'applicazione o in un altro wrapper, invece di inserire tali controlli in tutta l'applicazione.
Questo workaround dovrebbe essere temporaneo, perché i manutentori della libreria dovrebbero risolvere presto il bug.
Se nessuno ha condiviso questo workaround con la comunità, aggiungete un commento al problema che ha riportato la falla, e offrite la vostra soluzione agli altri.
A proposito, potreste determinare che la falla riportata colpisce una funzione che non state chiamando. Ma fate attenzione, perché potreste chiamare qualche altra funzione nella libreria che chiama indirettamente la funzione insicura. Ci sono strumenti di tracciamento e profilazione che vi permettono di guardare l'intera gerarchia delle chiamate di funzione nella vostra applicazione, così potete vedere se siete a rischio.
Forse siete proprio nel mirino degli attaccanti: state usando una funzione con una falla che non potete aggirare. Quindi considerate: avete bisogno della funzione con la falla? Ci sono spesso librerie alternative che offrono funzioni simili. Oppure l'uso particolare che state facendo della funzione potrebbe essere abbastanza semplice per voi da codificarla da soli. Ma scrivere la propria versione della funzione è una cattiva idea, perché è più probabile che introduciate dei bug piuttosto che risolvere il problema. Dopo tutto, siete anche meno esperti di codifica sicura dei manutentori della libreria. (Se siete più esperti di loro, aiutateli a sistemare la libreria!)
C'è anche la possibilità che vi sentiate abbastanza sicuri delle vostre capacità di codifica, e che abbiate abbastanza familiarità con il pacchetto che state usando, per offrire una correzione di bug. Questa è un'opzione solo per i pacchetti open source, ma spero che stiate usando pacchetti open source ogni volta che potete.
Non voglio concludere senza ricordare che la difesa in profondità è sempre importante. Per esempio, se la vostra applicazione è per uso interno, le regole del firewall e l'autenticazione dovrebbero assicurare che state comunicando solo con utenti legittimi. D'altra parte, anche un utente interno potrebbe essere malintenzionato, o potrebbe essere compromesso da un estraneo che lo usa come passaggio per entrare nel vostro server. Quindi un'applicazione sicura è ancora necessaria.
Conclusione
Le falle di sicurezza sono un rischio costante nello sviluppo del software, e sono tutte intorno a noi. Dovete stare attenti a queste vulnerabilità, perché una falla nel vostro sistema potrebbe permettere un attacco ransomware, il furto di dati sensibili dei clienti, lo sfruttamento del vostro sistema in una botnet, o qualche altro brutto risultato. Cercate di non essere vittime.
Ma ci sono così tante falle che non potete semplicemente assumere l'atteggiamento di eliminare ogni libreria contro cui è stata segnalata una falla. Se è disponibile un aggiornamento, installatelo prontamente. Negli altri casi, spero che questo articolo vi abbia aiutato a prendere decisioni ragionevoli per preservare la vostra sicurezza.