Dale Hagglund heeft gelijk. Dus ik ga gewoon hetzelfde zeggen, maar op een andere manier, met wat details en voorbeelden. ☺
Het juiste om te doen in de Unix en Linux werelden is:
- een klein, eenvoudig, gemakkelijk controleerbaar, programma hebben dat als superuser draait en de luisterende socket bindt;
- een ander klein, eenvoudig, gemakkelijk controleerbaar, programma hebben dat privileges laat vallen, gespawned door het eerste programma;
- het vlees van de service, in een apart derde programma hebben, dat draait onder een niet-superuser account en ketting geladen wordt door het tweede programma, in de verwachting dat het eenvoudig een open file descriptor voor de socket erft.
Je hebt het verkeerde idee van waar het grote risico zit. Het hoge risico zit in het lezen van het netwerk en het handelen naar wat er gelezen wordt, niet in het openen van een socket, het binden aan een poort, en het aanroepen van listen()
. Het is het deel van een dienst dat de eigenlijke communicatie doet dat het hoge risico vormt. De delen die openen, bind()
, en listen()
, en zelfs (tot op zekere hoogte) het deel dat accepts()
aanroept, zijn niet het hoge risico en kunnen worden uitgevoerd onder de bescherming van de superuser. Zij gebruiken en handelen niet op (met uitzondering van bron-IP-adressen in het geval van accept()
) gegevens die onder controle staan van onvertrouwde vreemden over het netwerk.
Er zijn vele manieren om dit te doen.
inetd
Zoals Dale Hagglund zegt, de oude “netwerk superserver” inetd
doet dit. De account waaronder het serviceproces wordt uitgevoerd is een van de kolommen in inetd.conf
. Het scheidt het luistergedeelte en het droppen van privileges niet in twee aparte programma’s, klein en gemakkelijk controleerbaar, maar het scheidt wel de hoofd-service code af in een apart programma, exec()
geïnstalleerd in een service proces dat het spawnt met een open file descriptor voor de socket.
De moeilijkheid om te auditen is niet zo'n probleem, omdat je maar één programma hoeft te auditen. Het grote probleem van inetd
is niet zozeer auditing, maar veeleer dat het geen eenvoudige fijnkorrelige runtime service controle biedt, vergeleken met meer recente gereedschappen.
UCSPI-TCP en daemontools
Daniel J. Bernstein’s UCSPI-TCP en daemontools pakketten zijn ontworpen om dit in samenhang te doen. Als alternatief kan men Bruce Guenter’s grotendeels gelijkwaardige daemontools-encore gereedschapsset gebruiken.
Het programma om de socket file descriptor te openen en te binden aan de geprivilegieerde lokale poort is tcpserver
, van UCSPI-TCP. Het doet zowel de listen()
als de accept()
.
tcpserver
spawnt dan ofwel een service programma dat zelf root privileges laat vallen (omdat het protocol dat geserveerd wordt inhoudt dat gestart wordt als de superuser en dan “inlogt”, zoals het geval is met, bijvoorbeeld, een FTP of een SSH daemon) of setuidgid
dat een op zichzelf staand klein en gemakkelijk controleerbaar programma is dat alleen privileges laat vallen en dan ketting-laadt naar het eigenlijke serviceprogramma (geen enkel deel daarvan draait dus ooit met superuser-privileges, zoals het geval is met, bijvoorbeeld, qmail-smtpd
).
Een service run
script zou dus bijvoorbeeld zijn (deze voor dummyidentd voor het leveren van de null IDENT service):
#!/bin/sh -e
exec 2>&1
exec \
tcpserver 0 113 \
setuidgid nobody \
dummyidentd.pl
nosh
Mijn nosh pakket is ontworpen om dit te doen. Het heeft een klein setuidgid
hulpprogramma, net als de anderen. Een klein verschil is dat het bruikbaar is met zowel systemd
-style “LISTEN_FDS” diensten als met UCSPI-TCP diensten, dus het traditionele tcpserver
programma is vervangen door twee aparte programma’s: tcp-socket-listen
en tcp-socket-accept
.
Ook hier spawnen en chainloaden utilities voor één doel elkaar. Een interessante eigenaardigheid van het ontwerp is dat men superuser-privileges kan laten vallen na listen()
maar zelfs vóór accept()
. Hier is een run
script voor qmail-smtpd
dat inderdaad precies dat doet:
#!/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 \
'
De programma’s die onder auspiciën van de superuser draaien zijn de kleine service-agnostische chain-loading tools fdmove
, clearenv
, envdir
, softlimit
, en tcp-socket-listen
. Op het moment dat setuidgid
wordt gestart, is de socket open en gebonden aan de sh
poort, en heeft het proces niet langer superuser rechten.
s6, s6-networking, en execline
Laurent Bercot’s s6 en s6-networking pakketten zijn ontworpen om dit in samenhang te doen. De commando’s lijken structureel erg op die van smtp
en UCSPI-TCP.
daemontools
scripts zouden vrijwel hetzelfde zijn, met uitzondering van de vervanging van run
voor s6-tcpserver
en tcpserver
voor s6-setuidgid
. Men zou er echter ook voor kunnen kiezen om gelijktijdig gebruik te maken van M. Bercot’s execline gereedschapsset.
Hier is een voorbeeld van een FTP service, lichtelijk aangepast van Wayne Marshall’s origineel , dat execline, s6, s6-networking, en het FTP server programma van publicfile gebruikt:
#!/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 is een andere toolset die op dezelfde manier werkt als ucspi-tcp en s6-networking. De tools zijn deze keer setuidgid
en chpst
, maar ze doen hetzelfde, en de hoog-risico code die het lezen, verwerken, en schrijven doet van dingen die over het netwerk worden gestuurd door onvertrouwde cliënten is nog steeds in een apart programma.
Hier is M. Pape’s voorbeeld van het draaien van tcpsvd
in een fnord
script:
#!/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
, het nieuwe service supervisie en init systeem dat in sommige Linux distributies gevonden kan worden, is bedoeld om te doen wat systemd
kan doen . Het gebruikt echter geen suite van kleine op zichzelf staande programma’s. Men moet inetd
in zijn geheel doorlichten, helaas.
Met systemd
maakt men configuratie bestanden om een socket te definieren waar systemd
op luistert, en een service die systemd
start. Het service “unit” bestand heeft instellingen waarmee men veel controle heeft over het service proces, inclusief als welke gebruiker het draait.
Met die gebruiker ingesteld als niet-superuser, doet systemd
al het werk van het openen van de socket, het binden aan een poort, en het aanroepen van systemd
(en, indien nodig, listen()
) in proces #1 als de superuser, en het serviceproces dat het spawnt draait zonder superuser-privileges.