Linux strežnik je lahko redno posodobljen, zaščiten s požarnim zidom in pravilno konfiguriran, pa ima še vedno storitve z bistveno več pravicami, kot jih dejansko potrebujejo.
Ko govorimo o varnosti Linux strežnikov, največkrat pomislimo na SSH, požarni zid, SELinux, AppArmor, Fail2ban in redne varnostne posodobitve.
Vse to je pomembno. Toda eno od precej uporabnih varnostnih orodij imamo na večini sodobnih Linux distribucij že nameščeno, pa ga pogosto premalo izkoriščamo.
To je systemd.
Večina administratorjev ga uporablja predvsem za zagon, ustavljanje in nadzor storitev. Vendar systemd omogoča tudi omejevanje dostopa do datotečnega sistema, privilegijev, naprav, omrežnih funkcij in nekaterih sistemskih klicev.
S pravilno konfiguracijo lahko precej zmanjšamo škodo, ki bi jo povzročila kompromitirana storitev.
Poglejmo deset nastavitev, ki jih je smiselno poznati.
1. NoNewPrivileges=true
Ena prvih nastavitev, ki jih je vredno preveriti, je:
NoNewPrivileges=true
Ta preprečuje, da bi proces in njegovi potomci pri izvajanju novih programov pridobili dodatne privilegije, na primer prek mehanizmov setuid ali datotečnih capabilities.
Pomembno je razumeti, da nastavitev ne odvzame privilegijev, ki jih proces že ima.
Če storitev že teče kot root, bo še vedno imela precejšnje pravice.
Zato je nastavitev najbolj uporabna v kombinaciji z izvajanjem storitve pod namenskim neprivilegiranim uporabnikom in dodatnimi omejitvami.
Kdaj previdno? Pri storitvah, ki morajo legitimno izvajati privilegirane pomožne programe.
2. PrivateTmp=true
Za številne aplikacije je imenik /tmp prostor za začasne datoteke.
Težava nastane, če storitev uporablja skupne začasne imenike, do katerih lahko dostopajo tudi drugi procesi.
Z nastavitvijo:
PrivateTmp=true
storitev dobi svoj zasebni pogled na /tmp in /var/tmp.
To zmanjša tveganje za določene napade prek začasnih datotek in nenamerne konflikte med aplikacijami.
Kdaj previdno? Če si aplikacije med seboj izmenjujejo datoteke prek skupnega /tmp, lahko takšna omejitev prekine njihovo delovanje.
3. ProtectSystem=strict
To je ena zanimivejših možnosti.
ProtectSystem=strict
Za storitev naredi skoraj celotno datotečno hierarhijo samo za branje, z določenimi izjemami za sistemske virtualne datotečne sisteme.
Aplikacija tako ne more kar poljubno spreminjati sistemskih konfiguracij in datotek.
Toda kaj, če mora pisati v /var/lib/myapp?
Za to obstaja nastavitev:
ReadWritePaths=/var/lib/myapp
Z njo določimo izjemo, ki omogoča pisanje na navedeno pot, če to dopuščajo tudi običajna dovoljenja in način priklopa datotečnega sistema.
Še boljša možnost za storitve, ki jih sami razvijamo, je pogosto uporaba StateDirectory= in RuntimeDirectory=.
Kdaj previdno? Pri podatkovnih bazah, strežniških aplikacijah in drugih storitvah, ki pišejo na več različnih lokacij. Pred uvedbo je treba popisati vse potrebne poti.
4. ProtectHome=true
Ali spletna storitev res potrebuje dostop do osebnih imenikov uporabnikov?
V večini primerov ne.
ProtectHome=true
Prepreči dostop do vsebine imenikov /home, /root in /run/user znotraj storitve.
To je še posebej koristno pri procesih, ki so dosegljivi prek omrežja.
Če pride do kompromitacije aplikacije, je ena od možnih poti napadalca iskanje uporabniških podatkov, konfiguracij in zasebnih ključev.
Ta nastavitev lahko takšen dostop omeji.
Kdaj previdno? Pri storitvah, ki namerno uporabljajo podatke v domačih imenikih, na primer nekaterih SFTP, backup in uporabniških aplikacijah.
5. PrivateDevices=true
Linux storitve običajno ne potrebujejo neposrednega dostopa do fizičnih naprav.
PrivateDevices=true
Storitev dobi omejen, zaseben pogled na /dev, z osnovnimi navideznimi napravami, ki jih običajne aplikacije potrebujejo.
S tem preprečimo neposreden dostop do številnih naprav, na primer diskov.
To je pomembno, ker lahko kompromitiran proces z ustreznimi privilegiji in dostopom do naprav povzroči precej več škode kot običajna uporabniška aplikacija.
Kdaj previdno? Pri storitvah za upravljanje strojne opreme, diskov, USB naprav, virtualizacije in drugih programih, ki potrebujejo dejanske naprave.
6. ProtectKernelTunables=true
Linux jedro omogoča spreminjanje številnih parametrov prek /proc/sys, /sys in podobnih vmesnikov.
Običajna aplikacija teh nastavitev ne bi smela spreminjati.
ProtectKernelTunables=true
Omeji možnost spreminjanja sistemskih nastavitev jedra znotraj storitve.
To pomaga zmanjšati tveganje, da bi kompromitirana aplikacija posegala v nastavitve operacijskega sistema.
Kdaj previdno? Pri namenskih sistemskih storitvah, ki morajo spreminjati parametre jedra.
7. ProtectKernelModules=true
Nalaganje modulov Linux jedra je privilegirana operacija.
Večina aplikacij tega ne potrebuje.
ProtectKernelModules=true
Preprečuje, da bi proces neposredno nalagal ali odstranjeval module jedra, in odstrani ustrezno capability iz dovoljenega nabora.
To ne pomeni, da jedro v nobenem primeru ne more samodejno naložiti modula zaradi drugih sistemskih dogodkov.
Pomeni pa, da ima sama storitev eno nevarno možnost manj.
Kdaj previdno? Pri storitvah za upravljanje modulov, virtualizacije in namenske strojne opreme.
8. ProtectControlGroups=true
Linux uporablja cgroups za upravljanje in omejevanje procesov ter sistemskih virov.
S pomočjo:
ProtectControlGroups=true
storitev ne more poljubno spreminjati hierarhije kontrolnih skupin prek običajnih vmesnikov cgroup.
Pri večini običajnih spletnih in aplikacijskih storitev za to ni potrebe.
Kdaj previdno? Pri upravljalnikih kontejnerjev in drugih storitvah, ki morajo same ustvarjati ali upravljati cgroups.
9. CapabilityBoundingSet=
Linux capabilities omogočajo razdelitev tradicionalnih privilegijev uporabnika root na manjše sklope.
Namesto da procesu dovolimo vse, mu lahko omejimo nabor zmogljivosti.
Na primer:
CapabilityBoundingSet=
Prazna vrednost odstrani vse capabilities iz dovoljenega nabora storitve.
To je lahko odlična izbira za preprosto aplikacijo, ki teče kot navaden uporabnik in ne potrebuje privilegiranih operacij.
Če potrebuje določeno capability, lahko dovolimo samo tisto, ki je res potrebna.
Kdaj previdno? Pri procesih, ki potrebujejo privilegirana omrežna vrata, določene sistemske operacije ali upravljanje drugih procesov.
Pri uporabi te nastavitve moramo razumeti tudi razliko med CapabilityBoundingSet= in AmbientCapabilities=.
10. SystemCallFilter=
Linux programi komunicirajo z jedrom prek sistemskih klicev.
Nekateri klici so namenjeni zelo specifičnim opravilom, ki jih običajna storitev ne potrebuje.
systemd omogoča filtriranje sistemskih klicev s pomočjo seccomp.
Primer:
SystemCallFilter=@system-service
Ta skupina vključuje sistemske klice, običajno potrebne za številne sistemske storitve.
S tem lahko omejimo dostop do drugih funkcionalnosti jedra.
Vendar skupina ni univerzalna rešitev. Nekateri jeziki, izvajalna okolja in aplikacije potrebujejo dodatne sistemske klice.
Kdaj previdno? Pri Javi, določenih runtime okoljih, aplikacijah s kompleksnim upravljanjem pomnilnika ter programih, ki uporabljajo posebne sistemske funkcije.
Filtre je treba preizkusiti tudi med dejanskimi opravili aplikacije, ne le ob njenem zagonu.
Kako vse skupaj uporabimo v praksi?
Najbolje je začeti pri eni manj kritični storitvi.
Recimo, da imamo lastno aplikacijo myapp.service, ki teče kot uporabnik myapp in svoje podatke shranjuje v /var/lib/myapp.
V systemd ustvarimo dodatno konfiguracijo, imenovano drop-in override:
sudo systemctl edit myapp.service
Vnesemo nastavitve, prilagojene dejanskim potrebam aplikacije.
Pomembno: vseh deset omejitev ne smemo na slepo uporabiti za vsako storitev. Konfiguracija, ki je primerna za preprosto spletno aplikacijo, lahko povsem onemogoči Docker, sistem za varnostne kopije ali podatkovno bazo.
Ko shranimo konfiguracijo, preverimo njeno veljavnost, ponovno zaženemo storitev in pregledamo dnevnike.
Spremembe, ki vplivajo na omrežje, zapisovanje datotek ali sistemske klice, lahko povzročijo težave šele pri določenih opravilih.
Zato samo uspešen zagon storitve še ni dokaz, da vse deluje.
Kako preverimo varnost storitve?
systemd ima uporabno vgrajeno orodje:
sudo systemd-analyze security myapp.service
Izpiše seznam varnostnih nastavitev in oceno izpostavljenosti.
Nižja ocena praviloma pomeni strožje omejitve systemd.
Pomembno pa je, da ta ocena ni celovita varnostna revizija.
Ne preverja vseh ranljivosti programske opreme, aplikacijske avtentikacije, pravil požarnega zidu in drugih zunanjih varnostnih mehanizmov.
Storitev z nizko oceno ima lahko še vedno kritično ranljivost. Storitev z visoko oceno pa ni nujno ranljiva.
Ocena je zato predvsem pomoč pri prepoznavanju možnosti za dodatno utrjevanje.
Zakaj ne bi preprosto vsega omogočili?
Ker varnost ni tekmovanje v številu omogočenih nastavitev.
Če storitvi odvzamemo nekaj, kar dejansko potrebuje, smo lahko povzročili izpad.
Pri produkcijskih Linux strežnikih zato priporočam postopno uvajanje.
Najprej pregled obstoječe konfiguracije, nato ena ali nekaj omejitev, funkcionalni preizkusi in šele potem nadaljnje utrjevanje.
Še posebej previdni moramo biti pri storitvah, kot so Docker, Podman, NFS, podatkovne baze, sistemi za varnostne kopije in programska oprema za virtualizacijo.
Te pogosto potrebujejo bistveno širši dostop do jedra, naprav ali datotečnega sistema.
Zaključek
Varnost Linux strežnika ni odvisna samo od tega, ali imamo nameščene zadnje posodobitve in pravilno nastavljen požarni zid.
Pomembno je tudi, kaj lahko posamezna storitev dejansko počne.
Če spletni aplikaciji ni treba dostopati do domačih imenikov uporabnikov, ji tega ni treba dovoliti.
Če proces ne potrebuje dostopa do fizičnih naprav, ga lahko omejimo.
In če aplikacija ne potrebuje privilegiranih operacij, ni razloga, da ji pustimo nepotrebne capabilities.
Najboljša varnostna nastavitev ni tista, ki omogoči največ omejitev, ampak tista, ki storitvi dovoli natanko toliko, kolikor potrebuje za svoje delo.
systemd nam za takšno načelo najmanjših privilegijev ponuja zelo uporabna orodja.
Le uporabiti jih moramo.
Ste že uporabili systemd sandboxing na svojih produkcijskih Linux strežnikih? Katere nastavitve so vam povzročile največ težav?