Perché dovrei chroot per sandboxing per sicurezza se la mia applicazione può iniziare dall'inizio a un livello inferiore?


14

Sto scrivendo un demone del server HTTP in C (ci sono ragioni per cui), gestendolo con il file di unità systemd.

Sto riscrivendo un'applicazione progettata 20 anni fa, intorno al 1995. E il sistema che usano è che chroot e poi setuid, e la procedura standard.

Ora nel mio lavoro precedente, la solita politica era che non avresti mai eseguito alcun processo come root. Si crea un utente / gruppo per esso e si esegue da lì. Naturalmente, il sistema ha eseguito alcune cose come root, ma siamo riusciti a ottenere tutta l'elaborazione della logica aziendale senza essere root.

Ora per il demone HTTP, posso eseguirlo senza root se non chroot all'interno dell'applicazione. Quindi non è più sicuro che l'applicazione non venga mai eseguita come root?

Non è più sicuro eseguirlo come mydaemon-user dall'inizio? Invece di avviarlo con root, chrooting, quindi setuid per mydaemon-user?


3
devi essere eseguito come root per usare la porta 80 o 443. in caso contrario, puoi fare ciò che fa tomcat e altri software webapp / web-server ed eseguire su una porta più alta (diciamo 8080, 9090, ecc.) e quindi usare apache / nginx per eseguire il proxy della connessione al software del server Web o utilizzare il firewall del sistema per NAT / inoltrare il traffico al server Web dalla porta 80. Se non è necessaria la porta 80 o 443, oppure è possibile eseguire il proxy o inoltrare la connessione, quindi non è necessario eseguire come root, in chroot o altro.
SnakeDoc,

3
@SnakeDoc su Linux non è più vero. Grazie a capabilities(7).
0xC0000022L

@SnakeDoc è possibile utilizzare authbind così
Abdul Ahad

Risposte:


27

Sembra che altri abbiano perso il tuo punto, il che non era un motivo per usare radici cambiate, cosa che ovviamente già sai chiaramente, né cos'altro puoi fare per porre limiti ai demoni, quando sai anche chiaramente di correre sotto l'egida di account utente senza privilegi; ma perché fare queste cose all'interno dell'applicazione . In realtà c'è un esempio abbastanza chiaro del perché.

Considera la progettazione del httpdprogramma dæmon nel pacchetto di file pubblici di Daniel J. Bernstein. La prima cosa che fa è cambiare root nella directory root che gli è stato detto di usare con un argomento di comando, quindi rilasciare i privilegi sull'ID utente non privilegiato e sull'ID gruppo che vengono passati in due variabili d'ambiente.

I set di strumenti di gestione Dæmon hanno strumenti dedicati per cose come la modifica della directory principale e il rilascio di ID utente e gruppo non privilegiati. La runit di Gerrit Pape ha chpst. Il mio set di strumenti nosh ha chroote setuidgid-fromenv. La s6 di Laurent Bercot ha s6-chroote s6-setuidgid. Perp di Wayne Marshall ha runtoole runuid. E così via. In effetti, tutti hanno il set di strumenti dei demoniol di M. Bernstein con setuidgidantecedenti.

Si potrebbe pensare di poter estrarre la funzionalità httpde utilizzare strumenti così dedicati. Quindi, come immagini, nessuna parte del programma server viene mai eseguita con privilegi di superutente.

Il problema è che uno come conseguenza diretta deve fare molto più lavoro per impostare la radice cambiata, e questo espone nuovi problemi.

Con Bernstein httpdcosì com'è, gli unici file e directory che si trovano nella struttura della directory principale sono quelli che devono essere pubblicati nel mondo. Non c'è nient'altro nell'albero. Inoltre, non esiste alcun motivo per l'esistenza di alcun file immagine del programma eseguibile in quell'albero.

Ma spostare il cambiamento directory principale fuori in un programma di catena di carico (o systemd), e improvvisamente il file immagine programma httpd, eventuali librerie condivise che carica, e tutti i file speciali /etc, /rune /devche il caricatore di programma o di runtime C accesso alla libreria durante l'inizializzazione del programma (che potresti trovare abbastanza sorprendente se tu truss/ straceun programma C o C ++), devi anche essere presente nella radice modificata. Altrimenti httpdnon può essere concatenato e non verrà caricato / eseguito.

Ricorda che questo è un server di contenuti HTTP (S). Può potenzialmente servire qualsiasi file (leggibile in tutto il mondo) nella radice modificata. Questo ora include cose come le tue librerie condivise, il tuo programma di caricamento programmi e copie di vari file di configurazione del programma di caricamento / CRTL per il tuo sistema operativo. E se per alcuni (accidentale) si intende che il server dei contenuti ha accesso a elementi di scrittura , un server compromesso può eventualmente ottenere l'accesso in scrittura all'immagine del programma per httpdse stesso o persino dal programma di caricamento del sistema. (Ricordate che ora avete due serie parallele di /usr, /lib, /etc, /run, e /devle directory per mantenere sicuro.)

Niente di tutto questo è il caso in cui httpdcambia root e rilascia i privilegi stessi.

Quindi hai scambiato con una piccola quantità di codice privilegiato, che è abbastanza facile da controllare e che gira proprio all'inizio del httpdprogramma, in esecuzione con privilegi di superutente; per avere una superficie di attacco notevolmente estesa di file e directory all'interno della radice modificata.

Questo è il motivo per cui non è così semplice come fare tutto esternamente al programma di servizio.

Si noti che questo è comunque un minimo indispensabile di funzionalità al httpdsuo interno . Tutto il codice che fa cose come cercare nel database degli account del sistema operativo l'ID utente e l'ID gruppo da inserire in quelle variabili di ambiente in primo luogo è esterno al httpdprogramma, in semplici comandi controllabili autonomi come envuidgid. (E, naturalmente, si tratta di uno strumento ucspi, in modo che contenga nessuna delle code di ascoltare sulla porta TCP in questione (s) o di accettare le connessioni, quelli che sono il dominio di comandi come tcpserver, tcp-socket-listen, tcp-socket-accept, s6-tcpserver4-socketbinder, s6-tcpserver4d, e così via.)

Ulteriori letture


+1, colpevole come addebitato. Ho trovato ambiguo il titolo e l'ultimo paragrafo, e se hai ragione mi sono perso il punto. Questa risposta fornisce un'interpretazione molto pratica. Personalmente noterei esplicitamente che dover costruire l'ambiente chroot in questo modo è uno sforzo extra, che la maggior parte delle persone vorrebbe evitare. Ma i 2 punti di sicurezza qui sono già ben fatti.
sourcejedi,

Un altro punto da ricordare è che se il server elimina i privilegi prima di elaborare qualsiasi traffico di rete, il codice privilegiato non viene esposto ad exploit remoti.
Kasperd,

5

Penso che molti dettagli della tua domanda potrebbero applicarsi allo stesso modo avahi-daemon, che ho esaminato di recente. (Potrei aver perso un altro dettaglio che differisce però). L'esecuzione di avahi-daemon in un chroot ha molti vantaggi, nel caso in cui avahi-daemon sia compromesso. Questi includono:

  1. non può leggere la home directory degli utenti ed esfiltrare informazioni private.
  2. non può sfruttare i bug di altri programmi scrivendo in / tmp. Esiste almeno un'intera categoria di tali bug. Ad esempio https://www.google.co.uk/search?q=tmp+race+security+bug
  3. non può aprire alcun file socket unix esterno al chroot, su cui altri daemon potrebbero ascoltare e leggere messaggi.

Il punto 3 potrebbe essere particolarmente utile quando non si utilizza dbus o simili ... Penso che avahi-daemon usi dbus, quindi si assicura di mantenere l'accesso al dbus di sistema anche dall'interno del chroot. Se non hai bisogno della possibilità di inviare messaggi sul dbus di sistema, negare tale capacità potrebbe essere una bella funzionalità di sicurezza.

gestendolo con il file di unità systemd

Si noti che se avahi-daemon è stato riscritto, potrebbe potenzialmente scegliere di fare affidamento su systemd per la sicurezza e utilizzare ad es ProtectHome. Ho proposto una modifica ad avahi-daemon per aggiungere queste protezioni come livello aggiuntivo, insieme ad alcune protezioni aggiuntive che non sono garantite da chroot. Puoi vedere l'elenco completo delle opzioni che ho proposto qui:

https://github.com/lathiat/avahi/pull/181/commits/67a7b10049c58d6afeebdc64ffd2023c5a93d49a

Sembra che ci siano più restrizioni che avrei potuto usare se avahi-daemon non avesse usato chroot stesso, alcune delle quali sono menzionate nel messaggio di commit. Non sono sicuro di quanto questo valga però.

Nota, le protezioni che ho usato non avrebbero limitato il demone dall'apertura di file socket unix (punto 3 sopra).

Un altro approccio sarebbe usare SELinux. In ogni caso, legheresti la tua applicazione a quel sottoinsieme di distribuzioni Linux. La ragione per cui ho pensato positivamente a SELinux qui, è che SELinux limita l'accesso che i processi hanno su dbus, in modo fine. Ad esempio, penso che ci si potrebbe aspettare spesso che systemdnon sia nell'elenco dei nomi di autobus a cui era necessario poter inviare messaggi a :-).

"Mi chiedevo se l'uso di sandboxing systemd fosse più sicuro di chroot / setuid / umask / ..."

Riepilogo: perché non entrambi? Decodificiamo un po 'quanto sopra :-).

Se si pensa al punto 3, l'uso di chroot fornisce più confinamento. ProtectHome = e i suoi amici non provano nemmeno a essere restrittivi come chroot. (Ad esempio, nessuna delle liste nere delle opzioni systemd nominate /run, in cui tendiamo a mettere file socket unix).

chroot mostra che limitare l'accesso al filesystem può essere molto potente, ma non tutto su Linux è un file :-). Esistono opzioni di sistema che possono limitare altre cose, che non sono file. Questo è utile se il programma è compromesso, è possibile ridurre le funzionalità del kernel disponibili, in cui potrebbe tentare di sfruttare una vulnerabilità. Ad esempio avahi-daemon non ha bisogno di socket bluetooth e immagino che il tuo server web non lo sia :-). Quindi non dargli accesso alla famiglia di indirizzi AF_BLUETOOTH. Autorizza solo AF_INET, AF_INET6 e forse AF_UNIX, usando l' RestrictAddressFamilies=opzione.

Leggi i documenti per ogni opzione che usi. Alcune opzioni sono più efficaci in combinazione con altre e alcune non sono disponibili su tutte le architetture della CPU. (Non perché la CPU è difettosa, ma perché la porta Linux per quella CPU non era così ben progettata. Penso).

(C'è un principio generale qui. È più sicuro se puoi scrivere elenchi di ciò che vuoi consentire, non di ciò che vuoi negare. Come definire un chroot ti dà un elenco di file a cui sei autorizzato ad accedere, e questo è più robusto che dire che vuoi bloccare /home).

In linea di principio, è possibile applicare personalmente tutte le stesse restrizioni prima di setuid (). È tutto solo codice che puoi copiare da systemd. Tuttavia, le opzioni di unità di sistema dovrebbero essere significativamente più facili da scrivere e, poiché sono in un formato standard, dovrebbero essere più facili da leggere e rivedere.

Quindi posso consigliare vivamente di leggere la sezione sandboxing man systemd.execsulla piattaforma di destinazione. Ma se si desidera che la maggior parte del disegno sicuro possibile, non avrei paura di provare chroot(e poi cadere rooti privilegi) nel programma pure . C'è un compromesso qui. L'utilizzo chrootimpone alcuni vincoli al design generale. Se hai già un design che utilizza chroot e sembra fare ciò di cui hai bisogno, suona piuttosto bene.


+1 soprattutto per i suggerimenti di systemd.
Mattdm,

ho imparato parecchio dalla tua risposta, se lo stack over flow permettesse una risposta multipla accetterei anche la tua Mi chiedevo se usare il sandbox di systemd fosse più sicuro di chroot / setuid / umask / ...
mur

@mur felice che ti sia piaciuto :). Questa è una risposta molto naturale alla mia risposta. Quindi l'ho aggiornato di nuovo, per provare a rispondere alla tua domanda.
sourcejedi,

1

Se puoi fare affidamento su systemd, allora è davvero più sicuro (e più semplice!) Lasciare il sandboxing su systemd. (Naturalmente, l'applicazione può anche rilevare se è stata avviata sandbox da systemd o meno e sandbox stessa se è ancora root.) L'equivalente del servizio che descriveresti sarebbe:

[Service]
ExecStart=/usr/local/bin/mydaemon
User=mydaemon-user
RootDirectory=...

Ma non dobbiamo fermarci qui. systemd può anche fare molto altro sandbox per te - ecco alcuni esempi:

[Service]
# allocate separate /tmp and /var/tmp for the service
PrivateTmp=yes
# mount / (except for some subdirectories) read-only
ProtectSystem=strict
# empty /home, /root
ProtectHome=yes
# disable setuid and other privilege escalation mechanisms
NoNewPrivileges=yes
# separate network namespace with only loopback device
PrivateNetwork=yes
# only unix domain sockets (no inet, inet6, netlink, …)
RestrictAddressFamilies=AF_UNIX

Vedi man 5 systemd.execper molte più direttive e descrizioni più dettagliate. Se rendi il tuo demone socket activatable ( man 5 systemd.socket), puoi anche usare le opzioni relative alla rete: l'unico collegamento del servizio al mondo esterno sarà il socket di rete che ha ricevuto da systemd, non sarà in grado di connettersi a nient'altro. Se è un server semplice che è in ascolto solo su alcune porte e non ha bisogno di connettersi ad altri server, questo può essere utile. (Le opzioni relative al file system possono anche rendere RootDirectoryobsolete, secondo me, quindi forse non è necessario preoccuparsi di impostare una nuova directory root con tutti i binari e le librerie richiesti.)

Supportano anche le versioni più recenti di systemd (dalla v232) DynamicUser=yes, in cui systemd allocerà automaticamente l'utente del servizio solo per il runtime del servizio. Questo significa che non c'è bisogno di registrare un utente permanente per il servizio, e funziona bene fino a quando il servizio non scrive a tutte le posizioni del file system diverso dal suo StateDirectory, LogsDirectorye CacheDirectory(che si può anche dichiarare nel file unità - vedi di man 5 systemd.execnuovo - e quale sistema gestirà, avendo cura di assegnarli correttamente all'utente dinamico).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.