Systemd Stufe 3 Security: Unterschied zwischen den Versionen

Aus Xinux Wiki
Zur Navigation springen Zur Suche springen
(systemd-run: Optionen live ausprobieren)
(Animation härten und untersuchen verlinkt)
 
Zeile 11: Zeile 11:
 
*'''systemd-analyze security''' bewertet, wie offen ein Dienst ist: 10 = alles offen, 0 = gut abgeschottet.
 
*'''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.
 
*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.
 +
 +
=Animation=
 +
*[https://labs.xinux.net/systemd-security.html systemd: härten und untersuchen]
  
 
=Teil A: Dienste härten=
 
=Teil A: Dienste härten=

Aktuelle Version vom 17. Oktober 2026, 15:53 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.

Animation

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

  1. Lassen Sie sich mit systemd-analyze security alle Dienste anzeigen. Welche drei sind am offensten?
  2. Legen Sie die ungehärtete simplehttp.service an und rufen Sie die Seite auf. Was sehen Sie?
  3. Härten Sie den Dienst Schritt für Schritt, bis er unter 4,0 liegt und die Seite weiter funktioniert.
  4. Härten Sie einen Dienst aus einem Paket per systemctl edit mit vier Optionen. Prüfen Sie den Wert vorher und nachher.
  5. Suchen Sie auf Ihrem System alle Unit-Dateien, die zu keinem Paket gehören. Können Sie jede erklären?

Für Schnelle

  1. Schalten Sie SystemCallFilter=@system-service ein und entfernen Sie dann @system-service schrittweise. Wann bricht der Dienst ab, und was steht im Journal?
  2. Richten Sie die auditd-Regel ein, legen Sie eine Test-Unit an und finden Sie das Ereignis mit ausearch.
  3. Prüfen Sie mit systemd-delta, welche Units auf Ihrem System verändert oder ergänzt wurden.

Vertiefung

Weiter