Systemd Stufe 3 Security: Unterschied zwischen den Versionen
Zur Navigation springen
Zur Suche springen
(Neue Seite: systemd Stufe 3 (Härten und Persistenz erkennen)) |
(systemd-run: Optionen live ausprobieren) |
||
| Zeile 173: | Zeile 173: | ||
*'''SystemCallErrorNumber=EPERM''' allein bewirkt nichts, es gehört immer zu einem SystemCallFilter=. | *'''SystemCallErrorNumber=EPERM''' allein bewirkt nichts, es gehört immer zu einem SystemCallFilter=. | ||
*'''Mit sed in Units schreiben''': '''sed -i -e''' schreiben, nicht '''sed -ie'''. Sonst entsteht eine Sicherungskopie mit „e“ am Ende im Unit-Verzeichnis. | *'''Mit sed in Units schreiben''': '''sed -i -e''' schreiben, nicht '''sed -ie'''. Sonst entsteht eine Sicherungskopie mit „e“ am Ende im Unit-Verzeichnis. | ||
| + | |||
| + | ==Optionen live ausprobieren mit systemd-run== | ||
| + | *Bevor man Optionen in eine Unit schreibt, kann man sie mit einer Shell im Käfig ausprobieren: | ||
| + | *systemd-run -t -p ProtectSystem=strict -p ProtectHome=yes -p PrivateTmp=yes /bin/bash | ||
| + | *In dieser Shell: | ||
| + | <pre> | ||
| + | touch /etc/test | ||
| + | touch: cannot touch '/etc/test': Read-only file system | ||
| + | ls /home | ||
| + | (leer) | ||
| + | </pre> | ||
| + | *Mit exit ist der Käfig wieder weg. | ||
| + | *Einen einzelnen Befehl mit Ausgabe: '''systemd-run -P -p DynamicUser=yes id''' | ||
| + | *So sieht man in Sekunden, was ein Dienst mit einer Option noch darf. | ||
=Teil B: Persistenz erkennen= | =Teil B: Persistenz erkennen= | ||
Version vom 17. Oktober 2026, 15:47 Uhr
| Stufe 1 · Basic | Stufe 2 · Fortgeschritten | Stufe 3 · Security |
|---|---|---|
| Ich verwende es | Ich schreibe Units | Ich härte und untersuche |
Einfach erklärt
- Ein Dienst darf von Haus aus fast alles, was sein Benutzer darf. Läuft er als root, darf er alles.
- systemd kann jeden Dienst in einen Käfig setzen: nur lesen statt schreiben, kein Zugriff auf /home, keine Geräte, nur bestimmte Systemaufrufe.
- systemd-analyze security bewertet, wie offen ein Dienst ist: 10 = alles offen, 0 = gut abgeschottet.
- Weil systemd Programme automatisch und oft als root startet, ist es auch ein beliebter Ort für Angreifer, um sich festzusetzen. Wer systemd kennt, findet sie.
Teil A: Dienste härten
Wie offen sind meine Dienste?
- systemd-analyze security
UNIT EXPOSURE PREDICATE HAPPY NetworkManager.service 7.8 EXPOSED 🙁 cron.service 9.6 UNSAFE 😨 ssh.service 9.6 UNSAFE 😨 systemd-journald.service 4.9 OK 🙂 systemd-logind.service 2.8 OK 🙂 systemd-timesyncd.service 2.1 OK 🙂 ...
- Die eigenen Dienste von systemd sind gut abgeschottet, viele Dienste aus Paketen gar nicht.
- UNSAFE heißt nicht „kaputt“, sondern: Wird der Dienst übernommen, hat der Angreifer viel Spielraum.
- Einzelner Dienst mit allen Punkten: systemd-analyze security ssh.service
- Eine Unit-Datei bewerten, bevor sie installiert ist: systemd-analyze security --offline=true ./datei.service
Ausgangslage
- /etc/systemd/system/simplehttp.service
[Unit] Description=Simple Http Server [Service] Type=exec ExecStart=/usr/bin/python3 -m http.server [Install] WantedBy=multi-user.target
- Wert: 9,4 UNSAFE
- Das größere Problem sieht man im Browser: Der Dienst läuft als root und ohne WorkingDirectory. http.server zeigt deshalb das komplette Dateisystem ab / an.
- Erste Regel beim Härten: Was soll der Dienst eigentlich können? Hier: als normaler Benutzer ein einziges Verzeichnis ausliefern.
Schritt für Schritt
- Gemessen mit systemd 257. Jede Zeile kommt zu den vorherigen dazu.
- Nach jedem Schritt:
systemctl daemon-reload systemctl restart simplehttp systemctl status simplehttp curl -s http://localhost:8000/ | head -3 systemd-analyze security simplehttp | tail -1
- Ein besserer Wert nützt nichts, wenn der Dienst danach nicht mehr läuft.
Die Optionen
| Bereich | Option | Wirkung |
|---|---|---|
| Dateisystem | ProtectSystem=strict | das ganze Dateisystem nur lesbar, außer /dev, /proc, /sys |
| ProtectHome=yes | /home, /root und /run/user unsichtbar | |
| PrivateTmp=yes | eigenes /tmp, nicht das gemeinsame | |
| PrivateDevices=yes | eigenes /dev nur mit null, zero, random und Co. | |
| ReadWritePaths=/pfad | Ausnahme: hier darf geschrieben werden | |
| Benutzer und Rechte | DynamicUser=yes | systemd legt beim Start einen Benutzer an und löscht ihn danach |
| NoNewPrivileges=yes | kein Aufstieg zu mehr Rechten, z.B. über sudo oder SUID-Programme | |
| CapabilityBoundingSet= | leer: keine Root-Sonderrechte, auch nicht für root | |
| RestrictSUIDSGID=yes | darf keine SUID/SGID-Dateien anlegen | |
| Kernel | ProtectKernelTunables=yes | /proc/sys und /sys nur lesbar |
| ProtectKernelModules=yes | darf keine Kernelmodule laden | |
| ProtectKernelLogs=yes, ProtectClock=yes, ProtectHostname=yes, ProtectControlGroups=yes | weitere Teile des Kernels gesperrt | |
| Netzwerk | RestrictAddressFamilies=AF_INET AF_INET6 | nur IPv4 und IPv6, keine anderen Socket-Arten |
| IPAddressDeny=any und IPAddressAllow=10.0.10.0/24 localhost | Firewall direkt am Dienst | |
| Systemaufrufe | SystemCallFilter=@system-service | nur die Aufrufe, die normale Dienste brauchen |
| SystemCallArchitectures=native | keine 32-Bit-Aufrufe auf 64-Bit-Systemen | |
| RestrictNamespaces=yes | darf keine eigenen Namespaces anlegen |
- Mehr Optionen: man systemd.exec
Das Ergebnis
[Unit] Description=Einfacher Webserver, gehärtet After=network.target [Service] Type=exec ExecStart=/usr/bin/python3 -m http.server 8000 WorkingDirectory=/srv/www DynamicUser=yes # Dateisystem ProtectSystem=strict ProtectHome=yes PrivateTmp=yes PrivateDevices=yes # Rechte NoNewPrivileges=yes CapabilityBoundingSet= RestrictSUIDSGID=yes # Kernel ProtectKernelTunables=yes ProtectKernelModules=yes ProtectKernelLogs=yes ProtectControlGroups=yes ProtectClock=yes ProtectHostname=yes # Netzwerk RestrictAddressFamilies=AF_INET AF_INET6 IPAddressDeny=any IPAddressAllow=10.0.10.0/24 localhost # Systemaufrufe und Prozesse SystemCallFilter=@system-service SystemCallArchitectures=native RestrictNamespaces=yes LockPersonality=yes MemoryDenyWriteExecute=yes RestrictRealtime=yes ProtectProc=invisible ProcSubset=pid UMask=0077 [Install] WantedBy=multi-user.target
- Wert: 1,4 OK
- Port 8000 liegt über 1024, deshalb braucht der Dienst auch CAP_NET_BIND_SERVICE nicht.
Dienste aus Paketen härten
- Nie die Unit unter /usr/lib ändern, sondern einen Drop-in anlegen:
- systemctl edit dienst
[Service] ProtectSystem=strict ProtectHome=yes PrivateTmp=yes NoNewPrivileges=yes
- So bleibt die Härtung bei Paket-Updates erhalten.
- Mit systemctl revert dienst ist alles wieder wie vorher.
Stolperfallen beim Härten
- Schreibt der Dienst Dateien, scheitert er an ProtectSystem=strict. Lösung: ReadWritePaths= oder besser StateDirectory=name (legt /var/lib/name passend an).
- DynamicUser=yes und eigene Dateien: Der Benutzer ist jedes Mal ein anderer. Dauerhafte Daten nur über StateDirectory=.
- MemoryDenyWriteExecute=yes verträgt sich nicht mit Programmen, die Code zur Laufzeit erzeugen, z.B. Java oder Node.js.
- SystemCallFilter=: Ruft der Dienst einen verbotenen Aufruf, wird er beendet. Im Status steht dann code=killed, signal=SYS. Zum Testen hilft SystemCallErrorNumber=EPERM: Dann bekommt der Dienst nur einen Fehler und läuft weiter.
- SystemCallErrorNumber=EPERM allein bewirkt nichts, es gehört immer zu einem SystemCallFilter=.
- Mit sed in Units schreiben: sed -i -e schreiben, nicht sed -ie. Sonst entsteht eine Sicherungskopie mit „e“ am Ende im Unit-Verzeichnis.
Optionen live ausprobieren mit systemd-run
- Bevor man Optionen in eine Unit schreibt, kann man sie mit einer Shell im Käfig ausprobieren:
- systemd-run -t -p ProtectSystem=strict -p ProtectHome=yes -p PrivateTmp=yes /bin/bash
- In dieser Shell:
touch /etc/test
touch: cannot touch '/etc/test': Read-only file system
ls /home
(leer)
- Mit exit ist der Käfig wieder weg.
- Einen einzelnen Befehl mit Ausgabe: systemd-run -P -p DynamicUser=yes id
- So sieht man in Sekunden, was ein Dienst mit einer Option noch darf.
Teil B: Persistenz erkennen
Warum systemd?
- Angreifer wollen nach einem Neustart wieder da sein. systemd startet Programme automatisch, oft als root, und eine Unit sieht harmlos aus.
- In MITRE ATT&CK:
- T1543.002: Systemd Service anlegen oder ändern
- T1053.006: Systemd Timer
- Für das Blue Team heißt das: Man muss wissen, wo Units liegen können, und erkennen, welche nicht dorthin gehören.
Wo man suchen muss
| Ort | Was dort normal ist | Worauf achten |
|---|---|---|
| /usr/lib/systemd/system/ | nur Dateien aus Paketen | Dateien, die zu keinem Paket gehören |
| /etc/systemd/system/ | eigene Units des Admins, Links in *.wants/ | unbekannte Units, neue Links, Drop-ins in *.d/ |
| /run/systemd/system/ | zur Laufzeit erzeugt | Units, die kein bekanntes Programm erzeugt hat |
| /etc/systemd/user/, ~/.config/systemd/user/ | selten benutzt | User-Units laufen mit den Rechten des Benutzers |
| /var/lib/systemd/linger/ | meist leer | Linger: User-Units laufen auch, wenn der Benutzer nicht angemeldet ist |
| */systemd/system-generators/ | wenige Generatoren aus Paketen | Generatoren laufen sehr früh beim Start, als root |
Werkzeuge
- Unit-Dateien, die zu keinem Paket gehören (Debian)
for f in $(find /etc/systemd/system /usr/lib/systemd/system -type f); do dpkg -S "$f" >/dev/null 2>&1 || echo "FREMD $f" done
- Rocky: rpm -qf "$f" statt dpkg -S
- Treffer in /etc sind oft gewollt, z.B. eigene Dienste. Jeder Treffer muss sich aber erklären lassen.
- Was wurde in letzter Zeit geändert?
- find /etc/systemd /usr/lib/systemd /run/systemd -type f -mtime -7 -ls
- Was überschreibt oder ergänzt Units der Pakete?
- systemd-delta --type=overridden,extended
[EXTENDED] /usr/lib/systemd/system/systemd-logind.service → /usr/lib/systemd/system/systemd-logind.service.d/dbus.conf ...
- Timer und User-Units
- systemctl list-timers --all
- ls /var/lib/systemd/linger/
- find /home /root -path '*/.config/systemd/user/*' -type f 2>/dev/null
- Verdächtige Befehle in Units
grep -r -n -E 'Exec.*(/tmp/|/dev/shm|/var/tmp|curl|wget|base64|bash -c|sh -c|nc )' \ /etc/systemd /usr/lib/systemd /run/systemd /home/*/.config/systemd 2>/dev/null
- Nicht jeder Treffer ist böse, aber jeder gehört angesehen.
- Paketdateien verändert?
- dpkg --verify
- Rocky: rpm -Va
- Was läuft gerade, und zu welcher Unit gehört es?
- systemd-cgls
- systemctl status <PID>
Überwachen statt suchen
- Mit auditd jede Änderung an Unit-Verzeichnissen melden:
-w /etc/systemd/system -p wa -k systemd_units -w /usr/lib/systemd/system -p wa -k systemd_units -w /etc/systemd/user -p wa -k systemd_units
- ausearch -k systemd_units
- Das Journal an ein zentrales Logsystem (SIEM) schicken: Journald Remote Logging
- Ein Angreifer mit root kann das lokale Journal löschen. Was schon beim SIEM ist, bleibt.
Stolperfallen
- Score ist nicht Sicherheit. Der Wert zeigt, wie eingesperrt ein Dienst ist, nicht, ob er Lücken hat.
- Härten ohne Test legt Dienste lahm. Immer schrittweise und mit Funktionstest.
- Nur /etc durchsuchen reicht nicht. User-Units, /run und Generatoren gehören dazu.
- Drop-ins übersehen. Eine harmlose Paket-Unit kann über einen Drop-in ein anderes ExecStart= bekommen. systemctl cat und systemd-delta zeigen es.
Aufgaben
Grundaufgaben
- Lassen Sie sich mit systemd-analyze security alle Dienste anzeigen. Welche drei sind am offensten?
- Legen Sie die ungehärtete simplehttp.service an und rufen Sie die Seite auf. Was sehen Sie?
- Härten Sie den Dienst Schritt für Schritt, bis er unter 4,0 liegt und die Seite weiter funktioniert.
- Härten Sie einen Dienst aus einem Paket per systemctl edit mit vier Optionen. Prüfen Sie den Wert vorher und nachher.
- Suchen Sie auf Ihrem System alle Unit-Dateien, die zu keinem Paket gehören. Können Sie jede erklären?
Für Schnelle
- Schalten Sie SystemCallFilter=@system-service ein und entfernen Sie dann @system-service schrittweise. Wann bricht der Dienst ab, und was steht im Journal?
- Richten Sie die auditd-Regel ein, legen Sie eine Test-Unit an und finden Sie das Ereignis mit ausearch.
- Prüfen Sie mit systemd-delta, welche Units auf Ihrem System verändert oder ergänzt wurden.
Vertiefung
- Systemd Hardening Workshop
- Systemd Sicherung Ihrer Dienste
- Systemd Fähigkeiten von Diensten wegnehmen
- Systemd AmbientCapabilities
- Systemd Dienst-Privat /tmp=
- Systemd Verzeichnisse als schreibgeschützt oder für Dienste nicht zugänglich machen
- Systemd InaccessibleDirectories
- Systemd Isolieren von Diensten vom Netzwerk
- Systemd PrivateNetwork
- Systemd Steuern des Geräteknotenzugriffs auf Dienste
- Systemd Verbieten von Forking, Einschränkung der Dateierstellung für Dienste
- Systemd Andere Optionen
