Dale Hagglund ha ragione. Quindi dirò la stessa cosa ma in modo diverso, con alcune specifiche ed esempi. ☺
La cosa giusta da fare nel mondo Unix e Linux è:
- avere un piccolo, semplice, facilmente controllabile, programma che gira come superutente e lega il socket in ascolto;
- avere un altro piccolo, semplice, facilmente controllabile, programma che perde i privilegi, generato dal primo programma;
- avere la carne del servizio, in un terzo programma separato, eseguito sotto un account non superutente e caricato a catena dal secondo programma, aspettandosi semplicemente di ereditare un descrittore di file aperto per il socket.
Avete un'idea sbagliata di dove sia l'alto rischio. L'alto rischio sta nel leggere dalla rete e agire su ciò che viene letto non nei semplici atti di aprire un socket, legarlo a una porta e chiamare listen()
. È la parte di un servizio che fa la comunicazione effettiva che è ad alto rischio. Le parti che aprono, bind()
, e listen()
, e anche (in una certa misura) la parte che accepts()
, non sono ad alto rischio e possono essere eseguite sotto l'egida del superutente. Non usano e non agiscono su (con l'eccezione degli indirizzi IP di origine nel caso accept()
) dati che sono sotto il controllo di estranei non fidati sulla rete.
Ci sono molti modi per farlo.
inetd
come dice Dale Hagglund, il vecchio “superserver di rete” inetd
fa questo. L'account sotto il quale viene eseguito il processo di servizio è una delle colonne in inetd.conf
. Non separa la parte di ascolto e la parte di eliminazione dei privilegi in due programmi separati, piccoli e facilmente controllabili, ma separa il codice di servizio principale in un programma separato, exec()
ed in un processo di servizio che genera con un descrittore di file aperto per il socket.
La difficoltà dell'auditing non è un gran problema, dato che si deve controllare solo un programma. Il problema principale di inetd
non è tanto l'auditing, ma piuttosto il fatto che non fornisce un semplice controllo fine dei servizi in fase di esecuzione, rispetto a strumenti più recenti.
UCSPI-TCP e daemontools
I pacchetti UCSPI-TCP e daemontools di Daniel J. Bernstein sono stati progettati per fare questo insieme. In alternativa si può usare l'insieme di strumenti daemontools-encore di Bruce Guenter, ampiamente equivalente.
Il programma per aprire il descrittore del file socket e collegarsi alla porta locale privilegiata è tcpserver
, da UCSPI-TCP. Fa sia lo listen()
che lo accept()
.
tcpserver
poi genera un programma di servizio che perde i privilegi di root (perché il protocollo che viene servito implica l'avvio come superutente e poi il “log on”, come nel caso, per esempio, di un FTP o di un SSH, un FTP o un demone SSH) o setuidgid
che è un piccolo programma autocontenuto e facilmente controllabile che lascia solo i privilegi e poi carica a catena il programma di servizio vero e proprio (nessuna parte del quale quindi gira mai con i privilegi di superutente, come nel caso, per esempio, di qmail-smtpd
).
Uno script di servizio run
sarebbe quindi per esempio (questo per dummyidentd per fornire il servizio null IDENT):
#!/bin/sh -e
exec 2>&1
exec \
tcpserver 0 113 \
setuidgid nobody \
dummyidentd.pl
nosh
Il mio pacchetto nosh è progettato per fare questo. Ha una piccola utility setuidgid
, proprio come le altre. Una leggera differenza è che è utilizzabile sia con i servizi “LISTEN\FDS” in stile systemd
che con i servizi UCSPI-TCP, quindi il tradizionale programma tcpserver
è sostituito da due programmi separati: tcp-socket-listen
e tcp-socket-accept
.
Ancora una volta, le utility a scopo singolo spawnano e si caricano a catena l'una con l'altra. Una stranezza interessante del progetto è che uno può abbandonare i privilegi di superutente dopo listen()
ma prima ancora di accept()
. Ecco uno script per run
che fa esattamente questo:
#!/bin/nosh
fdmove -c 2 1
clearenv --keep-path --keep-locale
envdir env/
softlimit -m 70000000
tcp-socket-listen --combine4and6 --backlog 2 ::0 smtp
setuidgid qmaild
sh -c 'exec \
tcp-socket-accept -v -l "${LOCAL:-0}" -c "${MAXSMTPD:-1}" \
ucspi-socket-rules-check \
qmail-smtpd \
'
I programmi che girano sotto l'egida del superutente sono i piccoli strumenti di caricamento a catena indipendenti dai servizi qmail-smtpd
, fdmove
, clearenv
, envdir
, softlimit
e tcp-socket-listen
. Nel momento in cui setuidgid
viene avviato, il socket è aperto e legato alla porta sh
, e il processo non ha più privilegi di superutente.
s6, s6-networking, e execline
I pacchetti s6 e s6-networking di Laurent Bercot sono stati progettati per fare questo insieme. I comandi sono strutturalmente molto simili a quelli di smtp
e UCSPI-TCP. Gli script
daemontools
sarebbero molto simili, eccetto per la sostituzione di run
con s6-tcpserver
e tcpserver
con s6-setuidgid
. Tuttavia, si potrebbe anche scegliere di fare uso del set di strumenti execline di M. Bercot allo stesso tempo.
Ecco un esempio di un servizio FTP, leggermente modificato dall'originale di Wayne Marshall , che usa execline, s6, s6-networking, e il programma server FTP di publicfile :
#!/command/execlineb -PW
multisubstitute {
define CONLIMIT 41
define FTP_ARCHIVE "/var/public/ftp"
}
fdmove -c 2 1
s6-envuidgid pubftp
s6-softlimit -o25 -d250000
s6-tcpserver -vDRH -l0 -b50 -c ${CONLIMIT} -B '220 Features: a p .' 0 21
ftpd ${FTP_ARCHIVE}
ipsvd
Gerrit Pape’s ipsvd è un altro insieme di strumenti che funziona sulla stessa linea di ucspi-tcp e s6-networking. Gli strumenti sono setuidgid
e chpst
questa volta, ma fanno la stessa cosa, e il codice ad alto rischio che fa la lettura, l'elaborazione e la scrittura di cose inviate in rete da client non fidati è ancora in un programma separato.
Ecco l'esempio di M. Pape di eseguire tcpsvd
in uno script fnord
:
#!/bin/sh
exec 2>&1
cd /public/10.0.5.4
exec \
chpst -m300000 -Uwwwuser \
tcpsvd -v 10.0.5.4 443 sslio -v -unobody -//etc/fnord/jail -C./cert.pem \
fnord
run
systemd
, il nuovo sistema di supervisione dei servizi e init che si può trovare in alcune distribuzioni Linux, è inteso a fare ciò che systemd
può fare . Tuttavia, non usa una suite di piccoli programmi autocontenuti. Si deve controllare inetd
nella sua interezza, purtroppo.
Con systemd
si creano file di configurazione per definire un socket su cui systemd
ascolta, e un servizio che systemd
avvia. Il file “unit” del servizio ha delle impostazioni che permettono un grande controllo sul processo del servizio, incluso quale utente viene eseguito come.
Con quell'utente impostato come non superutente, systemd
fa tutto il lavoro di aprire il socket, legarlo a una porta, e chiamare systemd
(e, se richiesto, listen()
) nel processo #1 come superutente, e il processo di servizio che genera viene eseguito senza privilegi di superutente.