Ormai da diverse release e, quindi, da molti anni, esiste sulle distribuzioni Red Hat e derivate uno strumento di sicurezza attivato di default che, originariamente sviluppato nel 2000 dalla National Security Agency e poi preso in mano da altri contributor del calibro di Red Hat, Network Associates, Secure Computing Corporation, Tresys Technology e Trusted Computer Solutions, permette un controllo dettagliato sulla sicurezza del sistema operativo e sul codice che esso gira.
Si tratta di un sistema particolarmente versatile e potente integrato nel Kernel Linux nel 2003 eppure, ne avevamo già parlato alcuni anni fa, la prima e unica istruzione su come gestirlo in molte documentazioni online è stata per anni quella per disabilitarlo. La situazione, ad oggi, è cambiata poco.
Nel dettaglio, SELinux si pone come un’architettura di sicurezza che fa uso dei Linux Security Modules e che consente una forte granularità nello stabilire, per ogni processo in esecuzione, cosa esso possa fare e cosa no. Ogni file, processo e persino le porte del sistema vengono associati a un tag, che contiene informazioni riguardo chi è autorizzato ad accedervi.
Questi tag hanno un formato che sicuramente risulterà già noto a molti di voi ed è utente:ruolo:tipo:livello. Questa non sarà una guida sull’amministrazione di SELinux ma vogliamo comunque mostrarvi direttamente ciò di cui si sta parlando con alcuni familiari comandi invocati con il parametro -Z:
[centos@centos7 ~]$ ls -Z drwxr-xr-x. centos centos unconfined_u:object_r:user_home_t:s0 Desktop drwxr-xr-x. centos centos unconfined_u:object_r:user_home_t:s0 Documents drwxr-xr-x. centos centos unconfined_u:object_r:user_home_t:s0 Downloads drwxr-xr-x. centos centos unconfined_u:object_r:audio_home_t:s0 Music drwxr-xr-x. centos centos unconfined_u:object_r:user_home_t:s0 Pictures drwxr-xr-x. centos centos unconfined_u:object_r:user_home_t:s0 Public drwxr-xr-x. centos centos unconfined_u:object_r:user_home_t:s0 Templates drwxr-xr-x. centos centos unconfined_u:object_r:user_home_t:s0 VideosCome notiamo, esiste un’informazione in più oltre a permessi, owner e gruppo, e sono i tag appena menzionati. Analogamente per le porte TCP\UDP, possiamo usare ss o netstat.
Un esempio di output per il secondo dei due:
[root@centos7 centos]# netstat -polentaZ Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State User Inode PID/Program name Security Context Timer tcp 0 0 0.0.0.0:111 0.0.0.0:* LISTEN 0 16714 745/rpcbind system_u:system_r:rpcbind_t:s0 off (0.00/0/0) tcp 0 0 192.168.122.1:53 0.0.0.0:* LISTEN 0 26057 1574/dnsmasq system_u:system_r:dnsmasq_t:s0-s0:c0.c1023 off (0.00/0/0) tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 0 24807 1233/sshd system_u:system_r:sshd_t:s0-s0:c0.c1023 off (0.00/0/0) tcp 0 0 127.0.0.1:631 0.0.0.0:* LISTEN 0 24478 1228/cupsd system_u:system_r:cupsd_t:s0-s0:c0.c1023 off (0.00/0/0) tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN 0 25128 1551/master system_u:system_r:postfix_master_t:s0 off (0.00/0/0) tcp 0 0 10.0.2.15:36630 34.211.241.174:443 ESTABLISHED 1000 126108 16140/firefox fined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 keepalive (103,58/0/0) tcp 0 0 192.168.57.25:22 192.168.57.1:64547 ESTABLISHED 0 30235 1937/sshd: centos [ system_u:system_r:sshd_t:s0-s0:c0.c1023 keepalive (3839,13/0/0) tcp 0 0 192.168.57.25:22 192.168.57.1:64583 ESTABLISHED 0 31078 2179/sshd: centos [ system_u:system_r:sshd_t:s0-s0:c0.c1023 keepalive (3183,77/0/0) tcp6 0 0 :::111 :::* LISTEN 0 16717 745/rpcbind system_u:system_r:rpcbind_t:s0 off (0.00/0/0) tcp6 0 0 :::22 :::* LISTEN 0 24809 1233/sshd system_u:system_r:sshd_t:s0-s0:c0.c1023 off (0.00/0/0) tcp6 0 0 ::1:631 :::* LISTEN 0 24477 1228/cupsd system_u:system_r:cupsd_t:s0-s0:c0.c1023 off (0.00/0/0) tcp6 0 0 :::3128 :::* LISTEN 0 123656 1270/(squid-1) system_u:system_r:squid_t:s0 off (0.00/0/0) tcp6 0 0 ::1:25 :::* LISTEN 0 25129 1551/master system_u:system_r:postfix_master_t:s0 off (0.00/0/0)Ciò che risulta immediato da notare è la pervasività di tale sistema che, taggando praticamente qualunque cosa, è in grado di offrire il controllo estremamente granulare di cui parlavamo poco fa, in quanto ogni accesso da parte di ogni processo (definito soggetto) verso una qualsiasi risorsa (definita oggetto) è ammessa solo se vi è un criterio che lo consente (anche detto policy) ed è impedita se un criterio specifico non c’è.
Tali criteri, come risulterà adesso apparente, funzionano facendo riferimento ai tag di cui sopra e si può, ad esempio, stabilire quali processi siano autorizzati a mettersi in ascolto su una determinata porta, o a quali cartelle possano avere accesso (in quest’ultimo caso, come misura ulteriore al già noto sistema dei permessi).
Dal momento che i criteri SELinux vengono controllati dopo quelli già presenti come i permessi dei file o i criteri per i sudoers, non è possibile dare accidentalmente più “permessi” di quanti un soggetto non ne abbia già, anche se la configurazione di tutto ciò non è sempre banale.
Informazioni dettagliate sull’amministrazione di SELinux sono disponibili nell’ampia documentazione Red Hat, ma vogliamo soffermarci sulla decisione di tenerlo abilitato o meno e sui compromessi che si vengono a creare.
Molti di voi sapranno che l’alternativa principale a SELinux è AppArmor, sistema simile che si trova nell’altra parte della grande famiglia del mondo Linux, ovvero le distribuzioni Debian, openSuse e derivate. Pur usando anch’esso i Linux Security Modules, è un sistema differente a cui dedicheremo del meritato spazio nei prossimi giorni, ma ha in comune con SELinux il fatto che tutto ciò che non è permesso esplicitamente da un criterio, semplicemente non è ammesso.
Questo aspetto chiave e imprescindibile dei due sistemi ci da un primo indizio sui ‘contro’: devono esistere delle policy impostate affinchè il sistema sia funzionante e, sebbene ciò sia spesso già così nel caso in cui tali sistemi arrivino di default sulla distro che stiamo installando, e tendano a includere una serie di profili preimpostati anche per i servizi più popolari come ssh e httpd, per tutto il resto le alternative sono due:
- si importa un profilo pre-esistente rilasciato dal vendor\maintainer
- si creano i criteri manualmente
Sebbene la seconda opzione non sia improponibile, è comunque uno sforzo che può variare dal banale (esempio: aggiungere un permesso per fare il bind su una porta diversa) al richiedere svariate decine di criteri che andranno poi testati e adattati a ogni cambio di codice.
La prima opzione è ovviamente la più auspicabile ed è abbracciata da progetti come mongodb, ma non tutti gli applicativi che ci servono ne hanno una e, in questo caso, è difficile giustificare lo sforzo di creare e testare le policy manualmente. Il fatto che si possano usare comandi come audit2allow per auto generare delle policy è di aiuto, ma non risolve del tutto il problema.
Cosa, però, ci stiamo ‘perdendo’? Che cosa offre davvero SELinux che viene perso se lo si mette in modalità Permissive, ovvero, di fatto, lo si disabilita lasciandolo soltanto loggare le violazioni?
Come al solito, tutto dipende dal nostro threat model: da cosa, insomma, vogliamo proteggerci? Risposte come ‘da tutto’ non sono pratiche, in quanto ogni sforzo di cybersecurity finisce per ‘mettersi in mezzo’ a ciò che è la comodità d’uso dei sistemi e, ispirandoci alla celebre frase di Gene Spafford, l’unico computer sicuro al 100% è uno spento (no, la disconnessione da Internet non basta) e tale macchina sarebbe poco utile.
Dov’è, quindi, che SELinux da il meglio di sè?
Consideriamo un server web su cui gira httpd in ascolto sulle porte tcp 80 e 443 e immaginiamo che la web application che gira venga compromessa e che un attaccante riesca quindi a far girare del codice arbitrario con la stessa utenza di httpd.
Una corretta e impeccabile impostazione dei permessi nel filesystem, a questo punto, è l’unica cosa che impedirebbe all’attaccante di leggere e scrivere sotto cartelle al di fuori della propria, inclusi vari share con svariate terabyte di dati riservati nel caso qualche utente abbia impostato le proprie cartelle a world-readable, ovvero 777, ovvero leggibili e scrivibili da chiunque, e sarebbe libero di mettersi in listening anche su un’altra porta, che si spera sarà bloccata dal firewall.
SELinux, in presenza di policy adeguatamente impostate, avrebbe impedito sia la prima parte, impedendo all’attaccante di leggere e scrivere fuori dalle cartelle designate per httpd, sia la seconda, impedendogli di mettersi in ascolto su altre porte che non siano la 80 e 443.
La domanda che potrebbe sorgere è: ci avrebbe forse protetto anche da una reverse shell che, per definizione, non richiederebbe di mettersi in ascolto su alcuna porta, o da un utente che riesce a diventare root tramite una vulnerabilità del Kernel stesso? Non è così scontato e una prima risposta sarebbe negativa, tuttavia una buona parte degli exploit richiedono una lista spesso lunga di condizioni che devono verificarsi affinchè l’attacco sia fattibile e sistemi come SELinux sono certamente un elemento di ostacolo per una non trascurabile fetta di attacchi con effetto mitigante anche quando non al 100% efficace e, se vi sono dei regolamenti di compliance da rispettare, sono un elemento che può renderci ‘compliant’.
Purtroppo Mandatory Access Control e granularità sono due parole che mal si sposano con la facilità di utilizzo e, nonostante la presenza di documentazione, non mancano a tutt’oggi recenti critiche a favore della sua disattivazione perchè, per una decente fetta di sysadmin, non si tratta di uno strumento di facile amministrazione, specie se vi sono sistemi complessi in essere o intere infrastrutture containerizzate, sebbene sia proprio lì che SELinux avrebbe il potenziale migliore grazie alla Multi Level Security. Non saremo noi certo a puntare il dito e condannare un comportamento (la disattivazione di una feature di sicurezza, per l’appunto), specie se dettato da esigenze interne o se si pone in ostacolo al core business, tuttavia è una buona occasione per riflettere ancora una volta su quanta cybersecurity sia sempre il solito compromesso e, quindi, non un assoluto (bianco o nero) ma meramente una sfumatura che dipende da quanto siamo disposti a investirci.