TL;DR. In questo articolo esploriamo gli schemi di sicurezza (hardening schemes) che funzionano out-of-the-box in cinque distribuzioni Linux popolari. Per ciascuna di esse abbiamo preso la configurazione predefinita del kernel, caricato tutti i pacchetti e analizzato gli schemi di protezione nei file binari annidati. Le distribuzioni considerate sono OpenSUSE 12.4, Debian 9, CentOS, RHEL 6.10 e 7, oltre a Ubuntu 14.04, 12.04 e 18.04 LTS.
I risultati confermano che anche gli schemi di base, come il canarino nello stack e il codice indipendente dalla posizione, non sono ancora utilizzati da tutti. La situazione è ancora peggiore per i compilatori quando si tratta di protezione dalle vulnerabilità come il conflitto dello stack (stack clash), che sono tornate alla ribalta a gennaio dopo la pubblicazione . Ma non è tutto così disperato. Una parte significativa dei binari implementa metodi di protezione di base, e il loro numero cresce di versione in versione.
La verifica ha mostrato che il numero maggiore di metodi di sicurezza è implementato in Ubuntu 18.04 a livello di sistema operativo e applicazioni, seguito da Debian 9. D'altra parte, anche OpenSUSE 12.4, CentOS 7 e RHEL 7 implementano schemi di sicurezza di base, mentre la protezione contro il conflitto dello stack è applicata ancora più ampiamente grazie a un set di pacchetti predefiniti decisamente più robusto.
Introduzione
Garantire un'elevata qualità del software è difficile. Nonostante la grande varietà di strumenti avanzati per l'analisi statica del codice e l'analisi dinamica in fase di esecuzione, nonché i significativi progressi nel sviluppo di compilatori e linguaggi di programmazione, il software moderno continua a soffrire di vulnerabilità che sono costantemente sfruttate dagli aggressori. La situazione è ancora più critica nelle ecosistemi che includono codice obsoleto. In tali casi, ci troviamo non solo ad affrontare il problema eterno di identificare possibili errori sfruttabili, ma ci troviamo anche vincolati a rigide limitazioni di retrocompatibilità che spesso richiedono di mantenere codice obsoleto, vulnerabile o difettoso.
Qui entrano in gioco i metodi di protezione o rafforzamento dei programmi (hardening). Alcuni tipi di errori non possiamo prevenire, ma possiamo rendere la vita più difficile agli aggressori e risolvere parzialmente il problema, prevenendolo o ostacolandolo. sfruttamento di questi errori. Questa protezione è utilizzata in tutti i moderni sistemi operativi, ma i metodi variano notevolmente in complessità, efficacia e prestazioni: dai canarini nello stack (stack canaries) e fino alle protezioni complete. e . In questo articolo esamineremo quali metodi di protezione sono applicati nelle distribuzioni Linux più popolari nella configurazione predefinita, e analizzeremo le caratteristiche dei binari distribuiti attraverso i sistemi di gestione dei pacchetti di ciascuna distribuzione.
CVE e sicurezza
Tutti noi abbiamo visto articoli con titoli come "Le applicazioni più vulnerabili dell'anno" o "I sistemi operativi più vulnerabili". Di solito, questi articoli presentano statistiche sul numero totale di registrazioni delle vulnerabilità di tipo , ottenute da da e altre fonti. Successivamente, queste applicazioni o sistemi operativi vengono classificati in base al numero di CVE. Sfortunatamente, sebbene i CVE siano molto utili per monitorare i problemi e informare fornitori e utenti, dicono poco sulla reale sicurezza del software.
Per esempio, consideriamo il numero totale di CVE negli ultimi quattro anni per il kernel Linux e i cinque più popolari distribuzioni server, ovvero Ubuntu, Debian, Red Hat Enterprise Linux e OpenSUSE.

Fig. 1
Cosa ci dice questo grafico? Un numero maggiore di CVE significa che una distribuzione è più vulnerabile di un'altra? La risposta è no. Ad esempio, in questo articolo vedrete che Debian ha meccanismi di protezione più rigorosi rispetto, ad esempio, a OpenSUSE o RedHat Linux, eppure Debian ha più CVE. Tuttavia, ciò non significa necessariamente una sicurezza compromessa: anche la presenza di CVE non indica se una vulnerabilità è sfruttabile. I punteggi di gravità offrono un'idea di quanto sia probabile l'uso delle vulnerabilità, ma in ultima analisi l'esploitabilità dipende in larga misura dalla protezione presente nei sistemi interessati, così come dalle risorse e dalle capacità degli aggressori. Inoltre, l'assenza di segnalazioni CVE non dice nulla su altre non registrate o sconosciute vulnerabilità. La differenza nel numero di CVE può essere spiegata non dalla qualità del software, ma da altri fattori, inclusi i fondi destinati ai test o la dimensione della base utenti. Nel nostro esempio, un numero maggiore di CVE in Debian potrebbe semplicemente indicare che Debian fornisce un numero maggiore di pacchetti software.
Naturalmente, il sistema CVE fornisce informazioni utili che consentono di creare protezioni adeguate. Più comprendiamo le cause dei guasti del programma, più è facile identificare i possibili modi di sfruttamento e sviluppare meccanismi adeguati di rilevamento e risposta. Nella Fig. 2 sono presentate le categorie di vulnerabilità per tutte le distribuzioni negli ultimi quattro anni (). È evidente che la maggior parte delle CVE ricade nelle seguenti categorie: attacchi di negazione del servizio (DoS), esecuzione di codice, overflow, corruzione della memoria, perdita (esfiltrazione) di informazioni ed escalation di privilegi. Sebbene molte CVE siano registrate più volte in diverse categorie, nel complesso i problemi rimangono sostanzialmente gli stessi di anno in anno. Nella prossima parte dell'articolo, valuteremo l'uso di schemi di protezione diversi per prevenire lo sfruttamento delle vulnerabilità sopra menzionate.

Fig. 2
Attività
In questo articolo intendiamo rispondere alle seguenti domande:
- Qual è la sicurezza dei diversi distribuzioni Linux? Quali meccanismi di protezione esistono nel kernel e nelle applicazioni dello spazio utente?
- Come è cambiata nel tempo l'adozione dei meccanismi di protezione per le varie distribuzioni?
- Quali sono le dipendenze medie di pacchetti e librerie per ciascuna distribuzione?
- Quali protezioni sono implementate per ogni binario?
Scelta delle distribuzioni
Risulta difficile trovare statistiche precise sulle installazioni di distribuzioni, poiché nella maggior parte dei casi il numero di download non riflette le installazioni reali. Tuttavia, le opzioni Unix rappresentano la maggior parte dei sistemi server (69,2% sui server web, secondo W3techs e altre fonti), e la loro quota continua a crescere. Pertanto, per la nostra ricerca ci siamo concentrati sulle distribuzioni disponibili di default sulla piattaforma . In particolare, abbiamo scelto i seguenti sistemi operativi:
Distribuzione/versione
Il kernel
Build
OpenSUSE 12.4
4.12.14-95.3-default
#1 SMP Wed Dec 5 06:00:48 UTC 2018 (63a8d29)
Debian 9 (stretch)
4.9.0-8-amd64
#1 SMP Debian 4.9.130-2 (2018-10-27)
CentOS 6.10
2.6.32-754.10.1.el6.x86_64
#1 SMP Tue Jan 15 17:07:28 UTC 2019
CentOS 7
3.10.0-957.5.1.el7.x86_64
#1 SMP Fri Feb 1 14:54:57 UTC 2019
Red Hat Enterprise Linux Server 6.10 (Santiago)
2.6.32-754.9.1.el6.x86_64
#1 SMP Wed Nov 21 15:08:21 EST 2018
Red Hat Enterprise Linux Server 7.6 (Maipo)
3.10.0-957.1.3.el7.x86_64
#1 SMP Thu Nov 15 17:36:42 UTC 2018
Ubuntu 14.04 (Trusty Tahr)
4.4.0–140-generic
#166~14.04.1-Ubuntu SMP Sat Nov 17 01:52:43 UTC 20…
Ubuntu 16.04 (Xenial Xerus)
4.15.0–1026-gcp
#27~16.04.1-Ubuntu SMP Fri Dec 7 09:59:47 UTC 2018
Ubuntu 18.04 (Bionic Beaver)
4.15.0–1026-gcp
#27-Ubuntu SMP Thu Dec 6 18:27:01 UTC 2018
Tabella 1
Analisi
Esamineremo la configurazione predefinita del kernel, così come le proprietà dei pacchetti disponibili tramite il gestore di pacchetti di ogni distribuzione di default. In questo modo consideriamo solo i pacchetti dai mirror predefiniti di ogni distribuzione, ignorando quelli provenienti da repository instabili (ad esempio, i mirror 'testing' in Debian) e pacchetti di terze parti (come i pacchetti Nvidia dai mirror standard). Inoltre, non consideriamo compilazioni personalizzate del kernel o configurazioni con maggiore sicurezza.
Analisi della configurazione del kernel
Abbiamo applicato uno script di analisi basato su . Consideriamo i parametri di sicurezza predefiniti delle distribuzioni menzionate e li confrontiamo con l'elenco fornito da (KSPP). Per ogni parametro di configurazione, la tabella 2 descrive l'impostazione desiderata: un segno di spunta indica le distribuzioni che seguono le raccomandazioni KSSP (per chiarimenti sui termini vedi ; negli articoli futuri spiegheremo come sono nati molti di questi metodi di protezione e come violare il sistema in loro assenza).

In generale, nei nuovi kernel le impostazioni sono più rigorose di default. Ad esempio, in CentOS 6.10 e RHEL 6.10 con kernel 2.6.32 mancano la maggior parte delle funzionalità critiche implementate nei nuovi kernel, come , rigorose autorizzazioni RWX, randomizzazione degli indirizzi o protezione copy2usr. È importante notare che molte delle opzioni di configurazione nella tabella non sono presenti nelle versioni precedenti del kernel e non sono realmente applicabili—nella tabella viene comunque indicato come mancanza di adeguata protezione. Analogamente, se un parametro di configurazione è assente in questa versione e per motivi di sicurezza deve essere disattivato, ciò è considerato una configurazione ragionevole.
Un altro aspetto da considerare nell'interpretazione dei risultati è che alcune configurazioni del kernel che aumentano la superficie di attacco potrebbero essere utilizzate anche per la sicurezza. Esempi di questo includono uprobes e kprobes, moduli del kernel e BPF/eBPF. La nostra raccomandazione è di utilizzare i meccanismi sopra citati per garantire una reale protezione, poiché non sono banali da usare e la loro sfruttabilità implica che i malintenzionati siano già stati radicati nel sistema. Tuttavia, se queste opzioni sono abilitate, l'amministratore di sistema deve monitorare attivamente gli abusi.
Analizzando ulteriormente le voci della tabella 2, vediamo che i kernel moderni offrono diverse opzioni per proteggere contro lo sfruttamento di vulnerabilità come le fughe di informazioni e il buffer/heap overflow. Tuttavia, notiamo che anche le distribuzioni più recenti non hanno ancora implementato protezioni più sofisticate (ad esempio, con patch ) o protezioni moderne contro gli attacchi di riutilizzo del codice (ad esempio, ). Ciò che è peggio, è che anche queste soluzioni di protezione più avanzate non difendono da tutta la gamma di attacchi. Pertanto, è fondamentale per gli amministratori di sistema integrare configurazioni adeguate con soluzioni che offrano rilevamento e prevenzione degli exploit durante il runtime.
Analisi delle applicazioni
Non sorprende che diversi distribuzioni abbiano diverse caratteristiche dei pacchetti, opzioni di compilazione, dipendenze delle librerie, ecc. Ci sono differenze anche per e pacchetti con poche dipendenze (ad esempio, coreutils su Ubuntu o Debian). Per valutare le differenze, abbiamo scaricato tutti i pacchetti disponibili, estratto il loro contenuto e analizzato i file binari e le dipendenze. Per ogni pacchetto, abbiamo tracciato gli altri pacchetti di cui dipende, e per ogni binario abbiamo monitorato le sue dipendenze. In questa sezione riassumeremo le conclusioni.
Distribuzioni
Abbiamo caricato in totale 361.556 pacchetti per tutte le distribuzioni, estraendo solo pacchetti dai mirror predefiniti. Abbiamo ignorato i pacchetti privi di file eseguibili ELF, come codici sorgente, font, ecc. Dopo la filtrazione, sono rimasti 129.569 pacchetti contenenti complessivamente 584.457 file binari. La distribuzione dei pacchetti e dei file per distribuzione è mostrata nella Fig. 3.

Fig. 3
Si può notare che più moderno è il sistema operativo, maggiore è il numero di pacchetti e file binari al suo interno, il che è logico. Tuttavia, i pacchetti di Ubuntu e Debian includono molti più file binari (sia eseguibili sia moduli e librerie dinamiche) rispetto a CentOS, SUSE e RHEL, il che potrebbe influenzare potenzialmente la superficie di attacco di Ubuntu e Debian (è importante notare che i numeri riflettono tutti i binari di tutte le versioni del pacchetto, quindi alcuni file vengono analizzati più volte). Questo è particolarmente importante considerando le dipendenze tra i pacchetti. Pertanto, una vulnerabilità in un file binario di un pacchetto può influenzare molte parti dell'ecosistema, proprio come una libreria vulnerabile può influenzare tutti i file binari che la importano. Come punto di riferimento, diamo un'occhiata alla distribuzione del numero di dipendenze tra i pacchetti nei vari sistemi operativi:
Fig. 4
In quasi tutte le distribuzioni, il 60% dei pacchetti ha almeno 10 dipendenze. Inoltre, alcuni pacchetti hanno un numero di dipendenze significativamente maggiore (oltre 100). Lo stesso vale per le dipendenze inverse dei pacchetti: come previsto, diversi pacchetti sono utilizzati da molti altri pacchetti nella distribuzione, quindi le vulnerabilità in questi pochi selezionati rappresentano un alto rischio. Ad esempio, nella tabella seguente vengono elencati 20 pacchetti con il maggior numero di dipendenze inverse in SLES, Centos 7, Debian 9 e Ubuntu 18.04 (in ogni cella è indicato il pacchetto e il numero di dipendenze inverse).

Tabella 3
Fatto interessante. Sebbene tutti i sistemi operativi analizzati siano progettati per l'architettura x86_64, e la maggior parte dei pacchetti abbia l'architettura definita come x86_64 e x86, i pacchetti contengono spesso file binari per altre architetture, come mostrato nella fig. 5.

Fig. 5
Nella prossima sezione approfondiremo le caratteristiche degli analizati binari.
Statistiche sulla protezione dei file binari
Come minimo assoluto, è necessario studiare il set di base delle opzioni di protezione per i file binari esistenti. Alcune distribuzioni Linux vengono fornite con script che eseguono tali controlli. Ad esempio, in Debian/Ubuntu esiste uno script di questo tipo. Ecco un esempio del suo funzionamento:
$ hardening-check $(which docker)
/usr/bin/docker:
Eseguibile indipendente dalla posizione: yes
Stack protetto: yes
Funzioni di Fortify Source: no, trovate solo funzioni non protette!
Ritaggi di sola lettura: yes
Binding immediato: yesLo script controlla cinque :
- Eseguibile Indipendente dalla posizione (PIE): indica se è possibile spostare in memoria la sezione di testo del programma per ottenere la randomizzazione, se ASLR è abilitato nel kernel.
- Stack Protetto: se le canarie dello stack sono attivate per proteggere contro gli attacchi di collisione dello stack.
- Fortify Source: se le funzioni non sicure (ad esempio, strcpy) vengono sostituite da versioni più sicure, mentre le chiamate verificate in tempo di esecuzione vengono sostituite da versioni non verificate (ad esempio, memcpy al posto di __memcpy_chk).
- Ritaggi di sola lettura (RELRO): se le voci della tabella di rilocazione sono contrassegnate come "sola lettura", se sono state attivate prima dell'esecuzione.
- Binding immediato: il linker dell'ambiente di esecuzione consente di risolvere tutte le relocazioni prima dell'esecuzione del programma (equivalente a RELRO completo).
Sono sufficienti i meccanismi sopra elencati? Purtroppo no. Esistono metodi noti per aggirare tutte le protezioni sopra menzionate, ma maggiore è la rigidità della protezione, più alta è la barriera per l'attaccante. Ad esempio, difficilmente possono essere applicati se sono attivi PIE e binding immediato. Allo stesso modo, ASLR completo richiede ulteriori sforzi per creare un exploit funzionante. Tuttavia, i malintenzionati più esperti sono già pronti a fronteggiare queste difese: la loro assenza accelererebbe essenzialmente l'attacco. Pertanto, è fondamentale che queste misure siano considerate necessarie. un minimo di.
Volevamo esaminare quanti file binari nelle distribuzioni considerate sono protetti da questi, oltre ad altri tre metodi:
- Il bit non eseguibile () impedisce l'esecuzione in qualsiasi regione che non dovrebbe essere eseguibile, ad esempio nello heap o nello stack, ecc.
- indica il percorso di esecuzione utilizzato dal caricatore dinamico per trovare le librerie appropriate. Il primo è obbligatorio. per qualsiasi sistema moderno: la sua assenza consente agli attaccanti di registrare liberamente il payload in memoria ed eseguirlo così com'è. Per il secondo, configurazioni errate del percorso di esecuzione aiutano nell'introduzione di codice non affidabile, il che può portare a una serie di problemi (ad esempio, , così come ).
- La protezione contro i conflitti di stack fornisce protezione contro attacchi che costringono lo stack a sovrapporsi ad altre aree di memoria (ad esempio, l'heap). Considerando i recenti exploit che abusano , abbiamo ritenuto opportuno includere questo meccanismo nel nostro set di dati.
Quindi, senza ulteriori indugi, passiamo ai numeri. Le tabelle 4 e 5 contengono un riepilogo dell'analisi dei file eseguibili e delle librerie di vari distribuzioni, rispettivamente.
- Come si può notare, la protezione NX è implementata ovunque, con rare eccezioni. In particolare, si può notare un uso leggermente più basso nelle distribuzioni Ubuntu e Debian rispetto a CentOS, RHEL e OpenSUSE.
- Le stack canary non sono presenti in molti luoghi, specialmente nelle distribuzioni con kernel obsoleti. Progressi sono stati osservati nelle ultime distribuzioni di CentOS, RHEL, Debian e Ubuntu.
- Tranne Debian e Ubuntu 18.04, la maggior parte delle distribuzioni presenta un supporto scarso per PIE.
- La protezione contro le collisioni dello stack è scarsamente implementata in OpenSUSE, CentOS 7 e RHEL 7 e praticamente assente negli altri.
- Tutte le distribuzioni con kernel moderni hanno un certo supporto per RELRO, con Ubuntu 18.04 in testa e Debian al secondo posto.
Come già accennato, le metriche in questa tabella sono medie su tutte le versioni del file binario. Se si considerano solo le ultime versioni dei file, i numeri saranno diversi (per esempio, vedi. ). Inoltre, la maggior parte delle distribuzioni di solito conta le statistiche verificando la protezione solo di alcune funzioni nel codice binario, mentre nella nostra analisi è indicato il vero percentuale di funzioni rinforzate. Pertanto, se nel binario sono protette 5 su 50 funzioni, daremo un punteggio di 0,1, che corrisponde al 10% di funzioni rinforzate.

Tabella 4. Caratteristiche di sicurezza per i file eseguibili, come mostrato in Fig. 3 (implementazione delle funzioni corrispondenti in percentuale rispetto al numero totale di file eseguibili)

Tabella 5. Caratteristiche di sicurezza per le librerie, come mostrato in Fig. 3 (implementazione delle funzioni corrispondenti in percentuale rispetto al numero totale di librerie)
C'è progresso? Assolutamente sì: è evidente dalle statistiche relative ai singoli distribuzioni (ad esempio, ), e anche dalle tabelle sopra riportate. Come esempio, in Fig. 6 viene mostrata l'implementazione dei meccanismi di sicurezza in tre distribuzioni consecutive di Ubuntu LTS 5 (abbiamo omesso le statistiche sulla protezione contro le collisioni dello stack). Notiamo che da una versione all'altra sempre più file supportano le canarie dello stack, e progressivamente sempre più file binari vengono forniti con la protezione completa RELRO.
Fig. 6
Sfortunatamente, alcuni file eseguibili in diverse distribuzioni non dispongono ancora di nessuna delle protezioni menzionate. Ad esempio, osservando Ubuntu 18.04, si può notare il binario ngetty (sostituto di getty), così come le shell mksh e lksh, l'interprete picolisp, i pacchetti nvidia-cuda-toolkit (un pacchetto popolare per applicazioni con accelerazione GPU, come i framework di machine learning) e klibc-utils. Analogamente, il binario mandos-client (uno strumento amministrativo che consente di riavviare automaticamente macchine con file system criptati), così come rsh-redone-client (una reimplementazione di rsh e rlogin), vengono forniti senza protezione NX, nonostante abbiano permessi SUID :(. Inoltre, in diversi binari SUID manca la protezione di base, come gli stack canaries (ad esempio, il file binario Xorg.wrap del pacchetto Xorg).
Riassunto e osservazioni finali
In questo articolo abbiamo messo in evidenza alcune caratteristiche di sicurezza delle moderne distribuzioni Linux. L'analisi ha mostrato che nell'ultima distribuzione LTS di Ubuntu (18.04) è implementata in media la protezione più robusta a livello di OS e applicazioni tra le distribuzioni con kernel relativamente recenti, come Ubuntu 14.04, 12.04 e Debian 9. Tuttavia, le distribuzioni considerate come CentOS, RHEL e OpenSUSE nel nostro set di dati forniscono di default un pacchetto più denso, e nelle ultime versioni (CentOS e RHEL) presentano una percentuale più alta di implementazione della protezione contro il conflitto dello stack, rispetto ai concorrenti basati su Debian (Debian e Ubuntu). Confrontando le versioni di CentOS e RedHat, notiamo significativi miglioramenti nell'implementazione delle canarini dello stack e di RELRO dalle versioni 6 a 7, ma in media CentOS implementa più funzionalità rispetto a RHEL. In generale, tutte le distribuzioni dovrebbero prestare particolare attenzione alla protezione PIE, che, a eccezione di Debian 9 e Ubuntu 18.04, è implementata in meno del 10% dei file binari nel nostro set di dati.
Infine, va notato: sebbene abbiamo condotto la ricerca manualmente, esistono molti strumenti di sicurezza (ad esempio, , , ), che eseguono analisi e aiutano a evitare configurazioni non sicure. Sfortunatamente, anche una protezione robusta con configurazioni ragionevoli non garantisce l'assenza di exploit. Ecco perché crediamo fermamente che sia vitale garantire , concentrandosi sui modelli di sfruttamento e prevenendoli.
Fonte: habr.com
