Come eseguire i servizi senza privilegi di root
Eseguire un servizio come root può trasformare una vulnerabilità in accesso completo al server. Usa un account dedicato o systemd con DynamicUser.
Perché non eseguire tutto come root
root può fare qualsiasi cosa sulla macchina: leggere ogni file, modificare qualsiasi impostazione, eliminare l’intero sistema. Quando esegui un servizio come root, concedi tutto questo potere al servizio. Se il servizio presenta una vulnerabilità che un attaccante può sfruttare, l’attaccante non ottiene soltanto il controllo del servizio: ottiene root, cioè l’intero server. Eseguire i servizi con un utente senza privilegi limita i danni. Una vulnerabilità in un servizio eseguito con un account limitato consente all’attaccante di accedere soltanto alle risorse che quell’account può utilizzare, che dovrebbero essere quasi nulle.
Questo è il principio del privilegio minimo: concedere a ogni componente del sistema esattamente gli accessi necessari per svolgere il proprio compito, e nulla di più. È l’abitudine più efficace per limitare l’impatto di una compromissione e, su un server moderno, applicarla ha un costo operativo quasi nullo.
Un account dedicato per ogni servizio
L’approccio classico consiste nel creare un utente di sistema separato per ogni servizio. L’utente deve possedere solo i file di quel servizio e non deve poter eseguire l’accesso. Un account di sistema per un’applicazione web può essere configurato così:
sudo useradd --system --no-create-home --shell /usr/sbin/nologin appsvcOgni flag ha una funzione precisa. --system indica che si tratta di un account di servizio, non di un accesso per utenti umani. --no-create-home evita di creare una home directory che non serve. --shell /usr/sbin/nologin impedisce di aprire una shell con quell’account, anche se un attaccante riuscisse in qualche modo a ottenerne le credenziali. L’account serve solo a possedere un processo e i relativi file.
Assegnate quindi a quell’utente soltanto i file necessari, senza concedere altro:
sudo chown -R appsvc:appsvc /opt/myappA questo punto il servizio può leggere e scrivere nella propria directory, ma non ha motivo di accedere ad altre parti del disco. Se viene sfruttato, i file che l’attaccante può modificare sono limitati a /opt/myapp. L’account può comunque leggere tutto ciò che è leggibile da chiunque, ma non può modificare il resto del sistema.
Fai eseguire il servizio a systemd con quell'utente
Dopo aver creato l'account, configura systemd in modo che esegua il servizio con quell'utente. Nel file dell'unità è sufficiente una riga:
[Service]
ExecStart=/opt/myapp/bin/server
User=appsvc
Group=appsvcUser=appsvc indica che il processo viene avviato con i privilegi limitati di quell'account, invece che con quelli di root. Questo è il metodo standard per eseguire un'applicazione con systemd e conviene adottarlo per ogni servizio per cui scrivi un'unità. La riduzione dei privilegi non è utile se systemd monitora il processo sbagliato. Se l'unità risulta ancora attiva dopo che il demone è terminato senza messaggi, verifica di aver scelto il Type= corretto in base al modo in cui viene avviato il processo.
Oppure evita del tutto l’account con DynamicUser
systemd può fare un passo ulteriore e creare per te un utente temporaneo, che esiste soltanto mentre il servizio è in esecuzione. Imposta DynamicUser=yes e non dovrai gestire alcun account:
[Service]
ExecStart=/opt/myapp/bin/server
DynamicUser=yes
StateDirectory=myappAll’avvio, systemd assegna un ID utente non utilizzato; all’arresto, lo rilascia. Il servizio riceve anche un /tmp privato, una vista in sola lettura della maggior parte del filesystem e una directory di stato scrivibile in /var/lib/myapp, che StateDirectory= crea e mette a sua disposizione. In SysV init non era possibile fare nulla di simile: la riduzione dei privilegi dipendeva da ciò che prevedeva lo script di avvio del singolo servizio. Questa limitazione spiega in larga parte perché le distribuzioni sono passate a systemd. Per un servizio autonomo che richiede soltanto la propria directory di stato, DynamicUser=yes è il modo più semplice per ottenere un isolamento efficace, perché non esiste alcun account persistente che un attaccante possa prendere di mira.
Scrivere manualmente le unità è macchinoso e gran parte del valore consiste nell’impostare correttamente le direttive di hardening. Il generatore nella guida ai servizi e ai timer di systemd può compilare queste opzioni al posto tuo, così l’unità sarà corretta fin dal primo tentativo.
Come si integra con il resto
Il principio del privilegio minimo è un livello di protezione e opera insieme agli altri, senza sostituirli. Un firewall con criterio predefinito di negazione controlla quali connessioni possono raggiungere il servizio; eseguirlo con un account senza privilegi limita ciò che il servizio può fare se viene violato; e un’istanza SSH rafforzata impedisce agli aggressori di accedere al server. Nessuna di queste misure è sufficiente da sola. Insieme fanno sì che una vulnerabilità in un servizio non comprometta l’intero server. Ospitare un servizio che protegge dati segreti mostra anche i limiti di questi livelli: un account con restrizioni limita ciò che un processo compromesso può raggiungere, ma un gestore di password self-hosted come Vaultwarden dipende comunque dalla protezione del relativo token amministrativo e del file di backup, aspetti che l’isolamento degli utenti non copre.
Prima di proseguire, esegui una checklist di hardening per l’intero server e genera una copia personalizzata su cui lavorare:
FAQ
Perché non dovrei eseguire un servizio come root?
Poiché root può eseguire qualsiasi operazione sulla macchina, se un servizio eseguito come root viene compromesso, l’attaccante ottiene il controllo dell’intero server, non soltanto del servizio. L’esecuzione del servizio con un account limitato e senza privilegi circoscrive i danni alle risorse accessibili a quell’account. Riservare root alle attività di amministrazione ed eseguire ogni servizio a lunga esecuzione con un utente limitato.
Come posso creare un utente che non può eseguire l’accesso?
Eseguire sudo useradd --system --no-create-home --shell /usr/sbin/nologin NAME. La shell nologin impedisce all’account di aprire una sessione interattiva anche se le sue credenziali vengono sottratte, --system lo identifica come account di servizio e --no-create-home evita di creare una directory home che non gli serve. Assegnare all’utente la proprietà soltanto dei file propri con chown.
Che cos’è systemd DynamicUser?
DynamicUser=yes indica a systemd di creare un utente temporaneo per il servizio, esistente soltanto mentre il servizio è in esecuzione. In questo modo non è necessario gestire un account permanente. Il servizio riceve inoltre un /tmp privato, una vista del filesystem prevalentemente in sola lettura e una directory di stato gestita. È il modo più semplice per eseguire un servizio autonomo con un’identità temporanea e con privilegi minimi.
L’esecuzione con un utente non root sostituisce un firewall?
No. Proteggono aspetti diversi. L’esecuzione con un utente senza privilegi limita ciò che un servizio può fare se viene compromesso, mentre un firewall limita ciò che può raggiungere il servizio. Usare entrambi, insieme a SSH configurato con criteri di sicurezza, in modo che ogni livello copra ciò che gli altri non possono proteggere.
Quali file deve possedere l’utente del servizio?
Soltanto i file effettivamente necessari al servizio, e nient’altro. Assegnare all’account la proprietà della directory di lavoro e dei relativi dati, lasciando tutto il resto di proprietà di root. Un approccio valido è usare sudo chown -R svc-app:svc-app /opt/svc-app per la directory dell’applicazione, mentre la configurazione in /etc resta di proprietà di root ed è soltanto leggibile dal servizio. L’obiettivo è limitare ai dati propri i file che il processo può modificare se viene compromesso, senza estendere l’accesso al resto del sistema.