Sicurezza su GNU/Linux

Su un sistema GNU/Linux siamo abituati a pensare alla sicurezza partendo da root, SSH, firewall, aggiornamenti e servizi esposti.
Tutto corretto.
Ma c’è un aspetto che spesso passa in secondo piano: cosa può trovare un attaccante una volta ottenuto l’accesso a un normale account?
Non serve necessariamente una nuova vulnerabilità per proseguire. A volte basta guardarsi intorno.
Una chiave SSH dimenticata, una password finita nella history, un file .env leggibile, una configurazione cloud o una regola sudo troppo permissiva possono trasformare un semplice accesso utente in qualcosa di decisamente più interessante.
Il concetto da tenere a mente è semplice:
Un account compromesso non è necessariamente il punto di arrivo. Molto spesso è solo il punto di partenza.
Partiamo da quattro aree particolarmente interessanti: credenziali, meccanismi di persistenza, configurazioni utili alla privilege escalation e directory temporanee utilizzabili per eseguire o depositare codice.
Proviamo però a guardare il problema con gli occhi di chi deve amministrare il sistema.
Una macchina racconta molte cose
Immaginiamo che qualcuno riesca a ottenere una shell con il nostro utente.
Non è root.
Potremmo essere tentati di pensare:
“Va bene, può fare poco.”
In realtà la prima fase interessante comincia proprio adesso.
Il sistema contiene una quantità enorme di informazioni utili per capire dove ci troviamo, cosa facciamo e soprattutto dove possiamo andare.
Anche senza privilegi particolari possiamo scoprire utenti, gruppi, processi, servizi, connessioni di rete e applicazioni installate.
Per un attaccante queste informazioni permettono di costruire una mappa.
E la nostra home è probabilmente il posto migliore da cui iniziare.
La home è una miniera
Dentro la home conserviamo anni di comodità accumulate.
Configurazioni, script, history, chiavi SSH, configurazioni di Docker, credenziali per servizi cloud e magari qualche password che avevamo promesso a noi stessi di eliminare subito dopo aver finito quel test.
Spoiler: è ancora lì.
Prendiamo la shell history:
~/.bash_history
~/.zsh_history
La history dovrebbe contenere semplicemente i nostri comandi.
Il problema nasce quando utilizziamo cose del genere:
mysql -u admin -pPasswordSuperSegreta
oppure:
curl -H "Authorization: Bearer TOKEN" https://api.example
o ancora variabili esportate direttamente dalla shell.
Il comando sparisce dal terminale, ma non necessariamente dalla history.
Vale quindi la pena dare ogni tanto un occhiata:
history
e magari cercare alcune parole interessanti:
grep -Ei "password|passwd|token|secret|apikey|authorization" ~/.bash_history
Questo non significa che ogni corrispondenza sia una password. Significa semplicemente che stiamo facendo quello che farebbe qualcuno entrato nel nostro account.
SSH: comodità e movimento laterale
Poi c’è:
~/.ssh/
Qui la situazione diventa ancora più interessante.
Possiamo trovare:
authorized_keys
config
known_hosts
id_ed25519
id_rsa
Una chiave privata senza passphrase può permettere di passare dalla macchina compromessa a un altro server.
Il file config può essere ancora più interessante:
Host backup
HostName 10.10.20.15
User backup
Host db-prod
HostName 10.10.30.20
User marvin
Abbiamo praticamente regalato una piccola mappa dell’infrastruttura.
Anche known_hosts può fornire indicazioni sui sistemi con cui abbiamo comunicato.
Questo è il classico movimento laterale: comprometto A e utilizzo quello che trovo su A per raggiungere B.
La protezione della chiave privata quindi non è un dettaglio:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519
chmod 600 ~/.ssh/authorized_keys
Ma soprattutto, quando possibile, utilizziamo una passphrase.
I permessi impediscono agli altri utenti locali di leggere la chiave. La passphrase ci protegge anche nel caso in cui il file venga copiato.
Sono due problemi differenti.
Non solo SSH
Oggi una macchina GNU/Linux difficilmente vive da sola.
Parla con database, registry, API, hypervisor, object storage e servizi cloud.
Ed ecco comparire file come:
~/.aws/credentials
~/.docker/config.json
~/.netrc
~/.pgpass
~/.my.cnf
oppure intere directory dedicate agli strumenti cloud.
Il principio rimane sempre lo stesso.
Una credenziale presente sulla macchina può permettere di uscire dalla macchina.
Ed è proprio questo il punto interessante: la compromissione di un server Linux può diventare la compromissione di qualcosa che Linux non è.
Sottolineiamo questo passaggio, includendo credenziali AWS, Azure e Google Cloud, configurazioni Docker e password per database tra gli obiettivi più interessanti dopo l’accesso iniziale.
E poi arrivano i file .env
Chi utilizza Docker o gestisce applicazioni web probabilmente ne ha parecchi.
Facciamo un esempio:
DB_HOST=db
DB_USER=app
DB_PASSWORD=password
API_TOKEN=qualcosa
SECRET_KEY=qualcosaltro
Comodissimo.
Ma se qualcuno riesce a leggerlo, abbiamo appena trasformato una compromissione locale in credenziali per altri servizi.
Possiamo iniziare cercando:
find /var/www /opt /srv /home -type f -name ".env" 2>/dev/null
Non dimentichiamoci inoltre di:
wp-config.php
e dei vari file config delle applicazioni.
Naturalmente trovare un file non significa avere un problema. La domanda corretta è:
chi può leggerlo?
stat /percorso/del/file
Least privilege vale anche per i file.
Mani in pasta
A questo punto facciamo un piccolo audit.
Non stiamo cercando malware.
Stiamo cercando di capire cosa troverebbe qualcuno utilizzando il nostro account.
Partiamo dai file scrivibili da tutti:
find / -xdev -type f -perm -0002 -ls 2>/dev/null
Poi le directory:
find / -xdev -type d -perm -0002 -ls 2>/dev/null
World-writable non significa automaticamente vulnerabile. Directory come /tmp devono esserlo per funzionare correttamente e utilizzano lo sticky bit proprio per limitare cosa possono fare gli utenti sui file altrui.
Ci interessa quindi il contesto, non soltanto il risultato del comando.
Controlliamo anche le chiavi autorizzate:
find /home /root -name authorized_keys -type f -print 2>/dev/null
Una chiave che non riconosciamo merita sicuramente qualche domanda.
sudo: la strada verso root
Altro comando che ogni amministratore dovrebbe conoscere:
sudo -l
Non serve leggere direttamente /etc/sudoers per capire cosa possiamo eseguire.
Una configurazione sudo troppo generosa può consentire a un normale utente di eseguire programmi con privilegi elevati.
E concedere NOPASSWD indiscriminatamente perché “tanto è più comodo” non è esattamente una strategia di hardening.
Vale la pena controllare:
/etc/sudoers
/etc/sudoers.d/
e soprattutto capire perché ogni eccezione esiste.
Se non ricordiamo perché abbiamo aggiunto una regola, probabilmente è arrivato il momento di rivalutarla.
Come tornare dopo un reboot
Una volta ottenuto un accesso, un attaccante ha un altro problema: mantenerlo.
GNU/Linux offre parecchi sistemi perfettamente legittimi per avviare automaticamente qualcosa.
Per esempio:
~/.bashrc
~/.profile
~/.config/systemd/user/
e naturalmente:
/etc/systemd/system/
/etc/cron.d/
/etc/cron.daily/
/var/spool/cron/
Il diagramma presente nel materiale di partenza riassume bene questa progressione: accesso iniziale, discovery, ricerca delle credenziali, escalation, persistenza e infine movimento verso altri sistemi.
Systemd è particolarmente interessante perché esistono anche servizi utente.
Controlliamoli:
systemctl --user list-unit-files --type=service
e quelli di sistema:
systemctl list-unit-files --type=service
Poi i timer:
systemctl list-timers --all
e naturalmente cron:
crontab -l
Non dobbiamo cercare solamente qualcosa con un nome palesemente sospetto.
Dobbiamo cercare qualcosa che non dovrebbe esserci.
La differenza sembra sottile, ma è enorme.
Il problema di /tmp
Abbiamo poi i classici:
/tmp
/var/tmp
/dev/shm
Sono luoghi legittimamente utilizzati da tantissimi software e proprio per questo possono diventare interessanti anche per attività indesiderate.
Verifichiamo inoltre /dev/shm e meccanismi come memfd_create per evidenziare un concetto importante: concentrarsi esclusivamente sui file persistenti presenti sul disco non offre una visione completa di quello che sta succedendo.
Possiamo almeno verificare come sono montati:
findmnt /tmp
findmnt /var/tmp
findmnt /dev/shm
Opzioni come:
nodev
nosuid
noexec
possono ridurre alcune possibilità di abuso.
Attenzione però a noexec: alcune applicazioni hanno realmente bisogno di eseguire qualcosa nelle directory temporanee.
La sicurezza applicata senza sapere cosa gira sul server è un ottimo sistema per trasformare un hardening in un downtime.
Non basta guardare i file
Ed eccoci probabilmente alla parte più importante.
Possiamo controllare tutti i file del mondo, ma dobbiamo osservare anche cosa sta facendo il sistema.
Processi:
ps auxf
porte:
ss -lntup
connessioni:
ss -ntup
servizi:
systemctl --type=service --state=running
utenti collegati:
w
ultimi accessi:
last
E naturalmente i log:
journalctl
Perché un file sospetto è interessante.
Un processo sospetto che apre una connessione verso Internet lo è ancora di più.
Qui strumenti come auditd, AIDE e sistemi centralizzati di raccolta dei log possono fare una grossa differenza, soprattutto perché ci permettono di confrontare quello che vediamo oggi con quello che consideravamo normale ieri.
Il concetto di baseline
Ed è qui che secondo me arriviamo al vero punto della questione.
Per sapere cosa è strano dobbiamo prima sapere cosa è normale.
Quanti utenti deve avere il server?
getent passwd
Chi può utilizzare sudo?
Quali porte devono essere aperte?
ss -lntup
Quali servizi devono partire automaticamente?
systemctl list-unit-files --state=enabled
Quali timer sono configurati?
systemctl list-timers --all
Quali filesystem sono montati?
findmnt
Quali container devono essere presenti?
docker ps
Una fotografia del sistema fatta quando sappiamo che tutto funziona correttamente vale tantissimo quando, mesi dopo, dobbiamo capire cosa è cambiato.
Una piccola checklist
Alla fine possiamo ridurre tutto a poche buone abitudini:
- proteggere le chiavi SSH e utilizzare una passphrase;
- evitare password, token e API key nella shell history;
- limitare i privilegi sudo allo stretto necessario;
- controllare periodicamente authorized_keys;
- evitare credenziali permanenti dentro script e file .env quando esistono alternative migliori;
- ruotare una credenziale se sospettiamo che sia stata letta;
- controllare cron, timer e servizi systemd, compresi quelli utente;
- monitorare processi e connessioni oltre al filesystem;
- conoscere la configurazione normale del server.
L’ultimo punto è probabilmente quello che costa meno e vale di più.
Conclusione
Proteggere root è fondamentale, ma proteggere soltanto root non basta.
Un normale utente può avere accesso a una chiave SSH che apre altri cinque server, una configurazione cloud con privilegi importanti o una password che permette di raggiungere il database di produzione.
A quel punto diventare root sulla prima macchina potrebbe persino non essere più l’obiettivo.
La prossima volta che accedete a uno dei vostri server provate quindi a non chiedervi soltanto:
“È sicuro?”
Provate con:
“Se qualcuno ottenesse la mia shell in questo momento, cosa troverebbe?”
È una domanda molto più scomoda.
E proprio per questo vale la pena farsela.
Le opinioni in quanto tali sono opinabili e nulla ti vieta di approfondire l’argomento.
Risorse: