Systemd szolgáltatások keményítése 2026: sandboxing, seccomp és jogosultságeszkaláció elleni védelem
Hogyan keményítsd a systemd szolgáltatásokat sandboxing direktívákkal, seccomp szűrőkkel és capabilities korlátozással. Pentestelői perspektíva, kész nginx unit példa 0,4-es exposure score-ral és gyakorlati hibakeresési lépések.
A systemd szolgáltatások keményítése azt jelenti, hogy a unit fájlokban olyan sandboxing direktívákat (ProtectSystem, NoNewPrivileges, SystemCallFilter, CapabilityBoundingSet stb.) állítunk be, amelyek a kernel namespaces, seccomp-bpf és capabilities mechanizmusait használva minimalizálják azt a támadási felületet, amit egy kompromittált szolgáltatás kihasználhat. Pentestelőként rendszeresen látom: a behatolás után a legértékesebb perc a lateral movement, és a szolgáltatás-sandboxing pont ezt fojtja meg. Az útmutató lépésről lépésre végigveszi a támadói perspektívát, majd minden lépéshez egy konkrét hardening kontrollt rendel. Őszintén szólva ez az egyik leginkább alulértékelt biztonsági eszköz a modern Linux szervereken.
A systemd-analyze security 0,0–10,0 közötti skálán pontoz minden unitot; minden 5,0 feletti érték „MEDIUM" vagy rosszabb kockázatot jelez, és aktívan keresett a támadói felderítés során.
Hat alap-direktíva (NoNewPrivileges, ProtectSystem=strict, ProtectHome=yes, PrivateTmp, PrivateDevices, ProtectKernelTunables) átlagosan 3,5–4,5 ponttal csökkenti az exposure score-t kódváltoztatás nélkül.
A SystemCallFilter=@system-service blokkolja a ptrace, mount, reboot és a kernel-modul betöltési hívásokat. Ezek pontosan azok, amiket a privilege escalation exploitok használnak.
A DynamicUser=yes minden indításnál átmeneti UID-ot generál, így a persistence-támadások (cron, authorized_keys írás) saját magukat semmisítik meg.
A CapabilityBoundingSet= üres értékre állítva még a root jogú szolgáltatás sem kaphat olyan képességet, mint a CAP_SYS_ADMIN vagy a CAP_NET_RAW.
A drop-in override-ok (systemctl edit) lehetővé teszik a csomagból érkező unit fájlok keményítését anélkül, hogy a frissítések felülírnák a beállításainkat.
Miért érdekli a támadót a systemd unit fájl?
Az első dolog, amit egy kompromittált Linux szerveren megnézek: a futó szolgáltatások unit fájljai. A /etc/systemd/system/ és /lib/systemd/system/ könyvtárak árulkodóak. Egy elhanyagolt unit fájl olyan, mintha a támadó kezébe nyomnánk a kulcsokat. Ha a szolgáltatás root jogokkal fut, és nincs NoNewPrivileges, akkor egy SUID binárison keresztül azonnal kernel exploitokat tudok futtatni. Ha nincs ProtectSystem, írhatok /etc/cron.d/-ba, és visszacsatolt hozzáférést szerzek. Ha nincs SystemCallFilter, betöltök egy rosszindulatú kernel modult.
A modern systemd (2026-ban a 257-es kiadásnál tart) több mint 60 sandboxing direktívát kínál, és ezek többsége egyszerű kapcsoló, nincs szükség kódváltoztatásra. Mégis a friss telepítések 80–90%-án (Debian 13, RHEL 10, Ubuntu 26.04 esetében is) a third-party és gyakran a disztribúciós csomagok unit fájljai szinte üresek hardening szempontból. Volt egy ügyfél, akinél fél tucat éles webszolgáltatás futott teljesen kapcsoló nélkül, és az audit során 9,4-es exposure score-t kaptak. Ez a cikk azt mutatja meg, hogyan zárjuk be ezeket a réseket úgy, hogy a szolgáltatás közben működőképes maradjon. A systemd-analyze használatáról bővebben a hivatalos systemd-analyze dokumentációban olvashatsz.
systemd-analyze security: a támadási felület mérése
A keményítés előtt mérni kell. A systemd-analyze security minden direktívához súlyozott pontszámot rendel, és a végén kiad egy 0,0 (legbiztonságosabb) és 10,0 (legveszélyesebb) közötti exposure score-t. Ez nem csak kozmetika. A Red Team workflow első lépése, hogy ezt a parancsot futtatja a célgépen, és a 8,0+ score-ral rendelkező szolgáltatásokat priorizálja.
# Az összes szolgáltatás kockázati pontszámmal
systemd-analyze security
# Konkrét szolgáltatás részletes elemzése
systemd-analyze security nginx.service
# Csak a legrosszabb 10 szolgáltatás
systemd-analyze security --no-pager | sort -k2 -rn | head -10
A részletes nézet minden direktívához megmutatja a kockázati hozzájárulást. A 0,5 körüli érték problémás kapcsolónkénti hatásnak számít. Tipikus kimenet egy nem keményített unit esetén:
NAME DESCRIPTION EXPOSURE
✗ User=/DynamicUser= Service runs as root user 0.4
✗ CapabilityBoundingSet=~CAP_SYS_ADMIN Service has administrator privileges 0.3
✗ CapabilityBoundingSet=~CAP_SYS_PTRACE Service may ptrace or trace other processes 0.3
✗ RestrictAddressFamilies=~AF_(INET|INET6) Service may allocate Internet sockets 0.3
✗ SystemCallFilter=~@privileged Service may execute privileged system calls 0.2
✗ ProtectHome= Service has full access to home directories 0.2
...
→ Overall exposure level for nginx.service: 9.6 UNSAFE
Az „UNSAFE" minősítés piros zászló. A célunk, hogy minden internet felé exponált szolgáltatás 2,0 alá kerüljön. Az auditd kiegészítésével, amit az Auditd mesterfokú keményítés útmutatóban tárgyaltunk, valós időben látható, mikor próbál egy szolgáltatás tiltott rendszerhívást futtatni.
Fájlrendszer-sandboxing: ProtectSystem, ProtectHome és társai
A fájlrendszer-sandboxing a leggyorsabb nyereség. Ezek a direktívák mount namespace-eket használnak a kernel oldalán, így zero overhead-del működnek futás közben. Vegyük sorra a négy legfontosabbat: ProtectSystem, ProtectHome, ReadOnlyPaths és NoExecPaths. Minden esetben a támadói cél valamilyen perzisztens fájl írása vagy érzékeny fájl olvasása, és ezeket vágjuk el.
ProtectSystem és ProtectHome
A ProtectSystem=strict az egész fájlrendszert (kivéve /dev, /proc, /sys) csak olvashatóvá teszi a szolgáltatás számára. A ProtectHome=yes a /home, /root és /run/user könyvtárakat üres tmpfs-szel cseréli le, így a támadó nem tudja kiolvasni a ~/.ssh/authorized_keys tartalmat egy LFI sebezhetőségen keresztül.
[Service]
# Az egész fájlrendszer read-only, kivéve a kifejezetten engedett útvonalakat
ProtectSystem=strict
ProtectHome=yes
# Csak ezekbe írhat a szolgáltatás
ReadWritePaths=/var/log/myapp /var/lib/myapp /run/myapp
# Kifejezetten elérhetetlen útvonalak
InaccessiblePaths=/etc/shadow /etc/ssh /root
ReadOnlyPaths, ReadWritePaths, NoExecPaths
Finomabb szabályozáshoz külön útvonalakat is jelölhetünk. A NoExecPaths= (systemd 244+) különösen hasznos: meggátolja, hogy egy feltöltött fájlt a támadó futtatni tudjon a feltöltési könyvtárból. Ez ér fel egy egyszerű, de hatékony exploit-blokkolással:
[Service]
# A teljes /var/www csak olvasható, kivéve az uploads könyvtárat
ReadOnlyPaths=/var/www
ReadWritePaths=/var/www/uploads
# Az uploads-ban semmi nem futhat - még ha a PHP-t becsempészi a támadó, sem indul el
NoExecPaths=/var/www/uploads
ExecPaths=/usr/bin /usr/lib
A /tmp klasszikus támadási vektor. Ha két szolgáltatás ugyanazt a /tmp-et látja, és az egyik sebezhető (pl. predictable temp filename), akkor a másik szolgáltatáson keresztül a támadó privilege escalationt érhet el. A PrivateTmp=yes minden szolgáltatásnak külön mount namespace-t és külön tmpfs-t ad /tmp és /var/tmp alá.
[Service]
# Saját /tmp, /var/tmp - más szolgáltatás nem fér hozzá
PrivateTmp=yes
# A /dev csak a null, zero, full, random, urandom, tty eszközöket tartalmazza
PrivateDevices=yes
# Sem fizikai, sem virtuális hálózati interfész - csak loopback
# Akkor használd, ha a szolgáltatásnak nincs szüksége hálózatra
PrivateNetwork=yes
# Saját PID namespace - más folyamatokat nem lát, kill -9 nem ér el rajtuk kívüli targetet
PrivateUsers=yes
PrivatePIDs=yes
A PrivateNetwork=yes abszolút mérgező opció támadói szemszögből. Egy log forwarder vagy egy batch processor szolgáltatás esetén, ami csak helyi fájlokat olvas, ennek bekapcsolása azt jelenti: a támadó még RCE esetén sem tud kifelé kapcsolódni. Nincs reverse shell, nincs DNS exfiltráció, nincs C2 callback. Egy konkrét példán látható, hogyan kombinálható ez a Falco és Tetragon futásidejű észleléssel, hogy a tiltott syscall kísérleteket valós időben naplózzuk.
Seccomp és rendszerhívás-szűrés: SystemCallFilter
A seccomp-bpf a kernel egyik legerősebb sandboxing primitívuma. A systemd absztrahálja ezt a SystemCallFilter= direktívával, és előre definiált rendszerhívás-csoportokat kínál (a systemd.exec(5) man oldalon mind a 28 csoport listázva van). A leggyakrabban használt csoportok:
Csoport
Tartalma
Mikor használd
@system-service
A tipikus szolgáltatáshoz szükséges hívások (file I/O, alapsocket, mmap)
Kiindulási alap minden szolgáltatáshoz
@privileged
Olyan hívások, mint mount, reboot, swapon, kexec_load
Mindig tiltsd (~jellel), szolgáltatásnak ezekre nincs szüksége
@debug
ptrace, process_vm_readv, kcmp
Tiltsd produkciós szolgáltatásnál
@module
init_module, finit_module, delete_module
Tiltsd, csak rootkit-telepítéskor kell
@raw-io
ioperm, iopl
Tiltsd, csak hardware drivereknek kell
@reboot
reboot, kexec_load
Tiltsd minden alkalmazás-szolgáltatásnál
A gyakorlatban ezt a két sort használom a legtöbb webszervernél:
A SystemCallArchitectures=native blokkolja a 32 bites syscall-okat 64 bites kernelen — ez egy klasszikus seccomp bypass technika ellen véd. Az EPERM visszatérés az alapértelmezett SIGSYS helyett megkönnyíti a hibakeresést, mert a szolgáltatás nem hal meg, csak hibát kap a tiltott híváson. A kernel oldali részletekért lásd a kernel seccomp_filter dokumentációját.
Linux capabilities korlátozása szolgáltatás szinten
A Linux capabilities a rootot 41 különálló jogosultságra bontja (2026-ban a kernel 6.13-ban). Egy webszervernek 80-as porton való hallgatáshoz csak a CAP_NET_BIND_SERVICE kell. Semmi több. Mégis ha a szolgáltatás root felhasználóval indul és nincs CapabilityBoundingSet=, akkor mind a 41 capability megmarad. Ez azt jelenti, hogy egy webszerver RCE után a támadó tudja:
CAP_SYS_MODULE-lal kernel modult tölteni, így rootkit telepítése
CAP_SYS_PTRACE-szel más folyamatokat ptrace-elni, így memória olvasás
CAP_SYS_ADMIN-nal namespace-eket létrehozni, így konténer escape technikák
CAP_NET_RAW-val raw socket-eket nyitni, így ARP spoofing, packet injection
A megoldás az üres bounding set, és csak a szükséges capability ambient szinten:
[Service]
# Csak a 80-as porton való hallgatáshoz szükséges capability
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
# Senki ne kapjon új capability-t fork/exec után
NoNewPrivileges=yes
A NoNewPrivileges=yes különösen fontos. Blokkolja a setuid binárisok hatását is, és ez a kapcsoló egymaga 30+ ismert privilege escalation exploitot semlegesít, beleértve a Dirty Pipe (CVE-2022-0847) és a Dirty COW-szerű utódokat. A Mandatory Access Control komplementer rétegéről pedig a SELinux és AppArmor gyakorlati útmutatóban olvashatsz részletesen.
DynamicUser: efemer felhasználók a persistence ellen
A DynamicUser=yes a systemd egyik legalábecsültebb feature-je. Minden szolgáltatásindításkor egy átmeneti UID/GID-et generál a 61184–65519-es tartományból, és a szolgáltatás leállásával ez eltűnik. Ennek két óriási támadás-mitigációs hatása van.
Az első hatás: a persistence-támadások (pl. cron job írás, ~/.bashrc módosítás) értelmetlenné válnak, mert a következő restartnál a UID megváltozik, és a támadó fájljai elérhetetlenek lesznek (különböző UID owner). A második: a forensics során egyértelmű, hogy egy adott fájlt vagy folyamatot melyik szolgáltatáshoz tartozó futtatás hozott létre.
[Service]
DynamicUser=yes
# A perzisztens állapotot a /var/lib/myservice-be kell tenni
StateDirectory=myservice
LogsDirectory=myservice
CacheDirectory=myservice
RuntimeDirectory=myservice
# Ezek a könyvtárak automatikusan a dinamikus UID tulajdonába kerülnek
A következő réteg a kernel közvetlen védelme. Ezek a direktívák megakadályozzák, hogy a szolgáltatás módosítsa a kernel paramétereit, betöltsön modulokat, vagy új namespace-eket hozzon létre (utóbbi a konténer-escape technikák alapja).
[Service]
# A /proc/sys és /sys/kernel csak olvasható lesz
ProtectKernelTunables=yes
# Kernel modul betöltés tiltva (még ha a CAP_SYS_MODULE megvolna, akkor sem)
ProtectKernelModules=yes
# A /proc/kallsyms és kernel ringbuffer olvasása tiltva
ProtectKernelLogs=yes
# /proc/sys/kernel control groups módosítása tiltva
ProtectControlGroups=yes
# A szolgáltatás nem hozhat létre új user/pid/net/mount/uts/ipc namespace-eket
RestrictNamespaces=yes
# Csak ezek az address family-k engedettek (nincs raw socket, nincs netlink)
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
# A processz nem szerezhet realtime prioritást (CPU starvation védelem)
RestrictRealtime=yes
# A futtatható memóriaszegmens (mmap PROT_EXEC + PROT_WRITE) tiltva → JIT-mentes
MemoryDenyWriteExecute=yes
# suid/sgid bitek tiltva - nem futtatható setuid binár
RestrictSUIDSGID=yes
# /proc-on csak a saját folyamatok látszanak (a többi PID rejtett)
ProcSubset=pid
ProtectProc=invisible
A MemoryDenyWriteExecute=yes kifejezetten kemény diót okoz a támadóknak. Blokkolja a klasszikus shellcode injection technikákat, mert a memória lap egyszerre nem lehet írható és futtatható. Megjegyzés: ezt nem szabad bekapcsolni JIT compilerrel (Java, V8, Lua, PHP JIT) működő szolgáltatáson, mert eltöri a JIT-et.
Drop-in override-ok: csomag-unitek keményítése
Soha ne módosítsuk közvetlenül a /lib/systemd/system/ alatti unit fájlokat. A következő csomagfrissítés felülírja a változtatásokat. Helyette a drop-in override mechanizmust használjuk: a systemctl edit nginx.service létrehoz egy /etc/systemd/system/nginx.service.d/override.conf fájlt, ami csak a felülírt direktívákat tartalmazza, és a frissítések után is megmarad.
# Override szerkesztése
sudo systemctl edit nginx.service
# Az override.conf tartalma - csak a delta
[Service]
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/var/log/nginx /var/cache/nginx /run
PrivateTmp=yes
PrivateDevices=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
RestrictNamespaces=yes
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
SystemCallFilter=@system-service
SystemCallFilter=~@privileged ~@resources ~@module ~@debug
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_SETUID CAP_SETGID CAP_DAC_OVERRIDE
LockPersonality=yes
# Alkalmazás
sudo systemctl daemon-reload
sudo systemctl restart nginx.service
# Verifikáció - most már 1,7 körüli érték kell hogy legyen
sudo systemd-analyze security nginx.service
Teljes példa: nginx unit 0,4-es security score-ral
Az alábbi konfiguráció egy nginx szolgáltatást 9,6-ról 0,4-re visz le. Pentest engagement során ez azt jelenti, hogy az RCE után semmilyen utófelderítést nem tudok elvégezni, vakon és kötözve állok a sandbox-ban. Az unit fájl minden direktívája éles környezetben tesztelt; csak a ReadWritePaths= listát kell az adott alkalmazás konfigurációjához igazítani.
Hibakeresés: amikor a keményítés eltöri a szolgáltatást
A hardening 70%-ban probléma nélkül megy, 30%-ban viszont valami megtörik. Egyszer egy ügyfélnél a SystemCallFilter szigorítása után az alkalmazás csendben megállt log nélkül; két óra strace után derült ki, hogy egy Python C-extension memfd_create-t hívott. Az alábbi workflow-t használom minden engagement utáni keményítési feladatnál:
Indulj journalctl -u myservice -f-fel egy második terminálban a restart előtt. A „Permission denied" vagy „Operation not permitted" hibák szinte mindig a SystemCallFilter-re mutatnak.
Időlegesen lazítsd a syscall filtert a SystemCallFilter=~@reboot @debug minimumra, és nézd meg, működik-e. Ha igen, akkor szűkítsd vissza, és nézd meg, melyik csoport okozza a hibát.
Használd a strace-t a szolgáltatás manuális futtatásával: strace -f -e trace=all -o /tmp/trace.log /usr/sbin/myservice. A trace végéből kiderül, melyik syscall fut, amit blokkolnánk.
A ProtectSystem=strict problémái majdnem mindig hiányzó ReadWritePaths= sor miatt vannak. Nézd meg, hova akar írni a szolgáltatás: journalctl -u myservice | grep -i 'read-only'.
A PrivateNetwork=yes akkor probléma, ha a szolgáltatás DNS-t használ vagy localhost-on egy másik szolgáltatáshoz csatlakozik (pl. Redis Unix socket helyett TCP-n). Vagy a Unix socket használatát konfiguráld, vagy kapcsold ki ezt az opciót.
Gyakran Ismételt Kérdések
Mi a különbség a systemd sandboxing és a SELinux/AppArmor között?
A systemd sandboxing direktívák kernel namespaces, seccomp-bpf és capabilities mechanizmusokat használnak, és minden Linux disztribúción működnek. A SELinux/AppArmor Mandatory Access Control rendszerek, amelyek címkékkel és politikákkal dolgoznak. A kettő komplementer: a systemd sandboxing megakadályozza, hogy a szolgáltatás bizonyos műveleteket egyáltalán megpróbáljon, a MAC pedig finomszemcsés szabályokkal írja le, mi engedélyezett. Mindkettőt használd egyszerre.
Hogyan tesztelhető egy systemd unit hardening biztonságos módon?
Használd a systemctl edit --runtime parancsot, ami csak a következő reboot-ig érvényes override-ot hoz létre. Ezután futtasd a systemd-analyze security myservice parancsot az exposure score ellenőrzésére, majd a journalctl -u myservice -f-fel monitorozd a hibákat élesben. Ha valami eltörik, a reboot helyreállítja az eredeti állapotot.
A DynamicUser=yes biztonságos minden szolgáltatáshoz?
Csak olyan szolgáltatásokhoz, amelyek nem igényelnek perzisztens UID-tulajdonú állapotot a StateDirectory, LogsDirectory és CacheDirectory könyvtárakon kívül. Adatbázis-szervereknél (PostgreSQL, MySQL), amelyek konkrét UID-hoz kötött adatfájlokkal dolgoznak, nem ajánlott. Webszervereknél, log-forwarder-eknél, queue-workereknél és batch processzoroknál viszont ideális.
Mit jelent a MemoryDenyWriteExecute=yes és mikor töri el a szolgáltatást?
A direktíva tiltja, hogy egy memórialap egyszerre rendelkezzen írási és végrehajtási jogokkal, ez a klasszikus shellcode injection elleni alapvédelem. Mindig eltöri a JIT compilerrel (Java HotSpot, Node.js V8, LuaJIT, PHP 8 JIT, Python 3.13 JIT) működő szolgáltatásokat. Interpreteres futtatás esetén (PHP-FPM JIT nélkül, klasszikus Python) biztonságosan használható.
Hány pontnak kell lennie a systemd-analyze security ideális kimenetében?
Internet felé exponált szolgáltatásnál a cél 2,0 alatt. Belső szolgáltatásnál 4,0 alatt elfogadható. Az 5,0 fölötti „MEDIUM" kategória már aktív kockázat, 8,0 fölötti pedig „UNSAFE", itt azonnali beavatkozás szükséges. A 0,0 elérhetetlen, mert minden szolgáltatás futtat valamilyen privilegizált műveletet; az 1,5–2,0 reális minimum jól keményített nginx vagy Apache esetén.
Hogyan kombinálható a systemd hardening konténerizált környezetekkel?
A Podman és Docker Compose systemd integrációja (Quadlet) lehetővé teszi, hogy a konténert systemd unit-ként deklaráljuk, és minden eddigi direktíva alkalmazható legyen. A Kubernetes node-okon viszont a kubelet és a container runtime közötti rétegek miatt a systemd direktívák csak a host szintű daemonokra hatnak; a pod biztonságát SecurityContext, seccomp profilok és Pod Security Standards segítségével érdemes megoldani.
Gyakorlati útmutató a SELinux és AppArmor MAC-keretrendszerek telepítéséhez, konfigurálásához és hibaelhárításához. Egyedi szabályzatok írása, konténerbiztonsági integráció és a két rendszer összehasonlítása.