Dale Hagglund ma rację. Więc powiem to samo, ale w inny sposób, z kilkoma konkretami i przykładami. ☺
Właściwą rzeczą do zrobienia w świecie Uniksa i Linuksa jest:
- mieć mały, prosty, łatwy do skontrolowania, program, który działa jako superużytkownik i wiąże gniazdo nasłuchujące;
- mieć inny mały, prosty, łatwy do skontrolowania, program zrzucający uprawnienia, wywołany przez pierwszy program;
- mieć mięso usługi, w oddzielnym trzecim programie, uruchomionym pod kontem nie-superużytkownika i ładowanym łańcuchowo przez drugi program, oczekując, że po prostu odziedziczy otwarty deskryptor pliku dla gniazda.
Masz złe wyobrażenie o tym, gdzie jest wysokie ryzyko. Wysokie ryzyko jest w czytaniu z sieci i działaniu na tym, co jest czytane, a nie w prostych czynnościach otwierania gniazda, wiązania go z portem i wywoływania listen()
. To jest część usługi, która wykonuje rzeczywistą komunikację, która jest wysokim ryzykiem. Części, które otwierają, bind()
, i listen()
, a nawet (w pewnym stopniu) część, która accepts()
, nie są wysokiego ryzyka i mogą być uruchamiane pod egidą superużytkownika. Nie używają one i nie działają na (z wyjątkiem źródłowych adresów IP w przypadku accept()
) danych, które są pod kontrolą niezaufanych obcych osób w sieci.
Można to zrobić na wiele sposobów.
inetd
Jak mówi Dale Hagglund, robi to stary “superserwer sieciowy” inetd
. Konto, pod którym uruchamiany jest proces usługi, jest jedną z kolumn w inetd.conf
. Nie rozdziela części nasłuchującej i części zrzucającej uprawnienia na dwa oddzielne programy, małe i łatwe do skontrolowania, ale oddziela główny kod usługi do oddzielnego programu, exec()
ed w procesie usługowym, który wywołuje z otwartym deskryptorem pliku dla gniazda.
Trudności z audytem nie są aż tak duże, ponieważ trzeba audytować tylko jeden program. Głównym problemem inetd
nie jest audyt, ale to, że nie zapewnia on prostej, drobnoziarnistej kontroli usług runtime, w porównaniu z nowszymi narzędziami.
UCSPI-TCP i daemontools
Pakiety Daniela J. Bernsteina UCSPI-TCP i daemontools zostały zaprojektowane by robić to w połączeniu. Alternatywnie można użyć w dużej mierze równoważnego zestawu narzędzi Bruce'a Guentera daemontools-encore .
Programem do otwierania deskryptora pliku gniazda i łączenia się z uprzywilejowanym portem lokalnym jest tcpserver
, z UCSPI-TCP. Wykonuje on zarówno listen()
jak i accept()
.
tcpserver
następnie wywołuje albo program usługowy, który sam zrzuca uprawnienia roota (ponieważ obsługiwany protokół wymaga rozpoczęcia pracy jako superużytkownik, a następnie “zalogowania się”, jak to ma miejsce w przypadku np, FTP lub demona SSH) lub setuidgid
, który jest samodzielnym, małym i łatwym do skontrolowania programem, który wyłącznie zrzuca przywileje, a następnie ładuje łańcuchowo do właściwego programu usługowego (żadna część tego programu nigdy nie działa z przywilejami superużytkownika, jak w przypadku, powiedzmy, qmail-smtpd
).
Skrypt usługi run
byłby na przykład taki (ten dla dummyidentd do zapewnienia usługi null IDENT):
#!/bin/sh -e
exec 2>&1
exec \
tcpserver 0 113 \
setuidgid nobody \
dummyidentd.pl
nosh
Mój pakiet nosh jest do tego przeznaczony. Posiada on małe narzędzie setuidgid
, tak jak inne. Jedną drobną różnicą jest to, że można go używać z usługami w stylu systemd
“LISTEN_FDS”, jak również z usługami UCSPI-TCP, więc tradycyjny program tcpserver
jest zastąpiony przez dwa oddzielne programy: tcp-socket-listen
i tcp-socket-accept
.
Ponownie, narzędzia jednozadaniowe spawnują i ładują się łańcuchowo jedno po drugim. Ciekawostką tego projektu jest to, że można pozbyć się uprawnień superużytkownika po listen()
, ale przed accept()
. Oto skrypt run
dla qmail-smtpd
, który rzeczywiście to robi:
#!/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 \
'
Programy, które działają pod egidą superużytkownika, to małe narzędzia do ładowania łańcuchów serwisowych fdmove
, clearenv
, envdir
, softlimit
, tcp-socket-listen
i setuidgid
. W momencie uruchomienia sh
, gniazdo jest otwarte i powiązane z portem smtp
, a proces nie ma już przywilejów superużytkownika.
s6, s6-networking, i execline
Pakiety s6 i s6-networking Laurenta Bercota zostały zaprojektowane do wykonywania tego w połączeniu. Komendy są strukturalnie bardzo podobne do tych z daemontools
i UCSPI-TCP. Skrypty
run
byłyby takie same, z wyjątkiem zamiany s6-tcpserver
na tcpserver
i s6-setuidgid
na setuidgid
. Jednakże, można również zdecydować się na użycie zestawu narzędzi execline M. Bercota w tym samym czasie.
Oto przykład usługi FTP, lekko zmodyfikowanej w stosunku do oryginału Wayne'a Marshalla , która używa execline, s6, s6-networking, oraz programu serwera FTP z 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
Gerrita Pape'a ipsvd jest kolejnym zestawem narzędzi, który działa na tych samych zasadach co ucspi-tcp i s6-networking. Tym razem są to narzędzia chpst
i tcpsvd
, ale robią to samo, a kod wysokiego ryzyka, który odczytuje, przetwarza i zapisuje rzeczy przesyłane przez sieć przez niezaufanych klientów jest nadal w oddzielnym programie.
Oto przykład M. Pape uruchomienia fnord
w skrypcie run
:
#!/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
systemd
systemd
inetd
, nowy system nadzoru usług i init, który można znaleźć w niektórych dystrybucjach Linuksa, [ ma robić to, co systemd
potrafi robić ]0x3&. Jednak nie używa on zestawu małych, samodzielnych programów. Niestety, trzeba audytować systemd
w całości.
Za pomocą systemd
tworzy się pliki konfiguracyjne definiujące gniazdo, na którym systemd
nasłuchuje, oraz usługę, którą systemd
uruchamia. Plik “unit” usługi ma ustawienia, które pozwalają na dużą kontrolę nad procesem usługi, w tym nad tym, jako jaki użytkownik jest uruchamiany.
Jeśli użytkownik jest ustawiony jako nie-nadużytkownik, listen()
wykonuje całą pracę związaną z otwieraniem gniazda, wiązaniem go z portem i wywoływaniem accept()
(oraz, jeśli jest to wymagane, 0x6&) w procesie #1 jako superużytkownik, a uruchamiany przez niego proces usługowy działa bez uprawnień superużytkownika.