...

Linux Capabilities: Root-Rechte sicher und granular aufteilen

Mit Linux Capabilities teile ich Root-Rechte in kleine, klar definierte Privilegien auf und senke damit das Risiko drastisch. So steuere ich gezielt, welche Prozesse besondere Aktionen ausführen dürfen, und begrenze die Angriffsfläche jeder Anwendung.

Zentrale Punkte

  • Feingranular statt allmächtig: Root-Rechte in kleinteilige Privilegien zerlegen.
  • File-Capabilities statt Set-UID: benötigte Rechte direkt an Binärdateien binden.
  • Capability-Sets steuern: Permitted, Effective, Inheritable, Bounding gezielt konfigurieren.
  • Privilege Separation: Dienste, Tools und Aufgaben strikt trennen.
  • Defense-in-Depth: Capabilities mit sudo, Rollen und Protokollen ergänzen.

Warum Root-Rechte trennen?

Ein Root-Konto gewährt Vollzugriff auf Datei- und Prozesswelt, doch genau das lädt zu folgenschweren Fehlern ein. Ein falscher Befehl oder ein Exploit genügt, und eine komplette Installation kippt. Ich begrenze darum weitreichende Aktionen auf das Nötigste und reduziere so Schadensausmaß und Wiederherstellungszeit. Das Prinzip der minimalen Rechte hält Dienste klein und kontrollierbar. Ich deaktiviere den direkten Root-Login, setze auf Rollen und schreibe lückenlose Protokolle.

Linux Capabilities kurz erklärt

Linux Capabilities zerlegen klassische Root-Macht in klar umrissene Privilegien. Jeder Prozess erhält nur die Bausteine, die seine Aufgabe wirklich braucht, etwa das Binden an Ports unter 1024 oder das Senden spezieller Signale. Damit umgehe ich das frühere Alles-oder-Nichts. Der Kernel verwaltet diese Bausteine je Prozess und setzt sie streng um. So bleibt die Kontrolle feinkörnig und nachvollziehbar.

Technisch verknüpfe ich Fähigkeiten entweder an Prozesse (über deren Capability-Sets) oder an Dateien (als Extended Attribute security.capability an ELF-Binaries). Beim execve()-Start verschmilzt der Kernel die File-Capabilities mit den Prozess-Sets: Vereinfacht gesagt, werden erlaubte Fähigkeiten aus dem Dateiattribut plus vererbbaren Rechten des aufrufenden Prozesses zum neuen Permitted-Set kombiniert und – wenn so markiert – zugleich im Effective-Set aktiviert. Das vermeidet Set-UID-Umwege und hält Privilegien sichtbar und prüfbar.

Capability-Sets im Prozesskontext verstehen

Jeder Prozess besitzt mehrere Mengen an Rechten, die ich gezielt steuere. Das Permitted-Set definiert, was ein Prozess grundsätzlich besitzen darf. Das Effective-Set legt fest, was gerade aktiv ist. Das Inheritable-Set regelt, welche Privilegien an Kindprozesse übergehen können. Das Bounding-Set zieht eine harte Obergrenze und verhindert, dass Prozesse darüber hinauswachsen.

Ambient Capabilities und Securebits

Neben den bekannten Sets gibt es das Ambient-Set, das beim execve() nicht automatisch verfällt. Ich nutze es, wenn ein unprivilegierter Prozess gezielt minimale Rechte über mehrere exec-Sprünge hinweg behalten soll (etwa beim Aufruf externer Hilfsprogramme). Ambient-Rechte fließen nur in die effektiven Rechte ein, wenn die aufgerufene Datei selbst keine File-Capabilities setzt – so verhindere ich ungewollte Eskalation.

Mit den Securebits steuere ich Details der Übergänge, z. B. ob ein Prozess nach dem Wechsel der UID seine zuvor gesetzten Fähigkeiten behalten darf (keepcaps) oder ob er generell keine neuen Privilegien erlangen darf (no_new_privs). In der Praxis setze ich Securebits eng und verzichte auf Komfort, um Exploit-Ketten zu brechen.

File Capabilities statt Set-UID

Ich ersetze Set-UID-Binaries durch File Capabilities, um das Risiko zu senken. Statt einem Programm Root zu geben, setze ich nur das benötigte Recht. Ein typischer Wechsel sieht so aus: setcap 'cap_net_bind_service=+ep' /usr/bin/meinserver. Mit getcap -r / prüfe ich, welche Dateien Fähigkeiten tragen. Das verkleinert Eskalationspfade spürbar.

Wichtig ist, dass File-Capabilities nur auf ELF-Binaries wirken. Interpreter-Skripte (z. B. Python, Bash) erben sie nicht zuverlässig. In solchen Fällen kapsle ich die privilegierte Aktion in ein kleines, statisch geprüftes Hilfsprogramm oder nutze socket activation, damit mein Dienst gar nicht erst selbst binden muss. Außerdem behalte ich Dateirechte im Blick: Capabilities verleihen Sonderrechte gegenüber dem Kernel, ersetzen aber keine üblichen ACLs oder POSIX-Permissions.

Beim Kopieren oder Packen gehen Fähigkeiten schnell verloren: cp ohne XATTR-Unterstützung, falsch gesetzte umask oder ein Build-Artefakt in einem Dateisystem ohne Extended Attributes entfernen security.capability stillschweigend. Ich arbeite deshalb reproduzierbar und nutze: cp --preserve=xattr ..., tar --xattrs, rsync -X. In Paket-Builds setze ich File-Capabilities explizit im Install-Skript, teste die Installation in einer sauberen VM und prüfe getcap im CI.

Privilege Separation mit realistischen Szenarien

Ein Webserver braucht Zugriff auf Port 80/443, nicht aber auf Kernel-Module oder Systemneustarts, daher setze ich CAP_NET_BIND_SERVICE und sonst nichts. Ein Backup-Agent darf Dateien lesen und schreiben, jedoch keine Netzwerkkonfiguration ändern. Ein Monitoring-Tool erhält Lesezugriffe auf Kennzahlen, bleibt aber ohne Änderungsrechte. Diese Zuschnitte machen Angriffe lokal, statt sie systemweit wirken zu lassen. Genau diese Trennung hält Dienste handhabbar und Fehlkonfigurationen in Schach.

Mit sudo und Rollen kombinieren

Capabilities ersetzen keine saubere Rollenstruktur, sie ergänzen sie. Ich vergebe sudo-Rechte eng, nutze vollständige Befehdspfade und halte mich fern von pauschalen Regeln wie „ALL=(ALL) ALL“. Jede Freigabe protokolliere ich. Gruppen bündeln Verantwortlichkeiten, während Capabilities technische Grenzen in Prozessen setzen. So entstehen klare Zuständigkeiten ohne Überrechte.

Häufige Fallstricke und Best Practices

  • Kein CAP_SYS_ADMIN als Abkürzung: Dieses Recht ist ein Sammelbecken. Ich ersetze es durch schmale Alternativen (z. B. CAP_SYS_CHROOT, CAP_SYS_TIME, CAP_SYS_NICE) oder verzichte ganz darauf.
  • Dateirechte bleiben strikt: Capabilities hebeln DAC nicht generell aus. Ohne CAP_DAC_OVERRIDE respektiert der Kernel weiterhin Besitzer und Modusbits. Ich vergebe Leserechte also weiterhin minimal.
  • Pfad-Härtung: Wenn ich ein Binary mit File-Capabilities versehe, verhindere ich PATH-Spoofing (absolute Pfade in sudoers, gesperrte Schreibrechte auf Verzeichnisse im Suchpfad).
  • Drop early, drop often: Prozesse starten ggf. mit mehr Rechten als nötig. Ich entferne überflüssige Fähigkeiten unmittelbar nach dem heiklen Schritt (prctl()/libcap) und setze no_new_privs, wo möglich.
  • Vererbung begrenzen: Ich halte das Inheritable- und Ambient-Set klein. Kinderprozesse dürfen keine neuen Türen öffnen.
  • Build- und Deploy-Pipeline prüfen: Ich validiere, dass security.capability erhalten bleibt und keine Staging-Schritte (Container-Layers, NFS, Artefakt-Scanner) XATTRs entfernen.

Übersicht wichtiger Capabilities und Risiken

Vor dem Zuweisen ordne ich benötigte Privilegien klar zu und prüfe ihr Risiko. Die folgende Tabelle zeigt typische Beispiele mit Wirkung und Einordnung. Ich berücksichtige immer Alternativen, um starke Rechte zu vermeiden. Gerade CAP_SYS_ADMIN weise ich äußerst sparsam zu. Wo möglich, ersetze ich weit gefasste Privilegien durch gezielte, schmale Varianten.

Capability Zweck Risiko Beispiel
CAP_NET_BIND_SERVICE An Ports < 1024 binden Niedrig bis mittel Webserver auf 80/443
CAP_SYS_BOOT System neu starten Hoch Geplanter Reboot
CAP_SYS_MODULE Kernel-Module laden/entfernen Sehr hoch Treiberverwaltung
CAP_SYS_ADMIN Vielseitige Admin-Operationen Sehr hoch Diverse Wartungsaufgaben
CAP_SETUID / CAP_SETGID UID/GID wechseln Mittel bis hoch Rechtewechsel im Dienst

Über die Tabelle hinaus bewerte ich gerade CAP_SYS_PTRACE (Prozesse debuggen), CAP_NET_ADMIN (Netzwerkparametrisierung) und CAP_DAC_OVERRIDE (Dateizugriffsbeschränkungen umgehen) äußerst kritisch. Oft gibt es Muster, die diese Rechte vermeiden: dedizierte Metrik-Endpunkte statt Prozess-Snooping, socket activation oder Port-Weiterleitungen statt Bind-Rechten und saubere Dateirechte statt pauschaler DAC-Umgehung.

Härtung in Containern und Hosting

In Multi-Tenant-Umgebungen halte ich Fähigkeiten radikal klein und verhindere Vererbung in Kinderprozesse. Container profitieren deutlich, sobald das Bounding-Set straff sitzt. Ich kombiniere das mit isolierten Filesystem- und Prozessräumen. Für den Überblick über Isolationsansätze hilft mir dieser Einstieg zu Prozess-Isolation. So bleiben Dienste getrennt, selbst wenn eine Anwendung schwächelt.

Praktisch setze ich Container standardmäßig auf „alles droppen, gezielt hinzufügen“: --cap-drop=ALL --cap-add=NET_BIND_SERVICE für Webdienste, keine Mount-Rechte, kein SYS_ADMIN. In orchestrierten Umgebungen halte ich das Profil zentral vor und prüfe es in Policies. Wichtig: Ich verlasse mich nicht auf File-Capabilities im Image, sondern vergebe Laufzeit-Rechte im Orchestrator – reproduzierbar und auditierbar.

Zusammenspiel mit SELinux und AppArmor

Capabilities steuern, was ein Prozess darf, während MAC-Profile festlegen, worauf er zugreift, und beides harmoniert gut. Ich setze Capabilities eng und lasse SELinux oder AppArmor den Zugriff auf Dateien und Sockets einschränken. Dadurch entsteht Schichtschutz, der Exploits mehrere Hürden vorschiebt. Einen schnellen Vergleich finde ich hier: SELinux vs. AppArmor. Damit bleibt ein kompromittierter Dienst eingeschlossen und kann weniger Schaden verursachen.

Praxis: Schritt-für-Schritt vorgehen

Ich starte mit einer Bestandsaufnahme aller Dienste und ihrer Anforderungen. Danach entferne ich unnötige Set-UID-Binaries oder ersetze sie durch gezielte File Capabilities. Sudo konfiguriere ich restriktiv und dokumentiere jeden Eintrag. Rollen und Gruppen ordne ich Aufgaben zu und halte Rechte minimal. Anschließend teste ich unter Last und prüfe Logeinträge auf unerwartete Ablehnungen.

Eine knappe Checkliste hilft mir beim Umstieg:

  • Anforderungen je Dienst schriftlich fixieren (nur das, was wirklich gebraucht wird).
  • Bestehende Sonderrechte inventarisieren (find / -perm -4000, getcap -r /).
  • Gezielt ersetzen: Set-UID abschaffen, File-Capabilities setzen, Rechte frühzeitig droppen.
  • Vererbungen schließen: Bounding-Set straffen, Inheritable/Ambient minimieren.
  • Systemd-/Container-Profile absichern (CapabilityBoundingSet=, NoNewPrivileges=yes).
  • Unter Last testen, Logs und Audit-Einträge prüfen, Ausnahmen dokumentieren.

Monitoring, Namespaces und kontinuierliche Audits

Ich überwache Logdateien, Alarme und Syscalls, damit unerwünschte Aktionen sofort auffallen. Änderungen an Capabilities, sudo-Regeln und Rollen validiere ich regelmäßig. Wo sinnvoll, trenne ich Workloads per Kernel-Isolationsmechanismen zusätzlich auf. Einen guten Startpunkt liefert dieser Überblick zu Namespaces und Cgroups. So erkenne ich Abweichungen früh und halte die Umgebung sauber.

Für den Alltag nutze ich einfache Prüfungen: capsh --print zeigt mir den aktuellen Satz an Fähigkeiten, getpcaps <PID> listet Prozessrechte und in /proc/<PID>/status lese ich CapEff, CapPrm, CapBnd. Mit auditd verfolge ich Änderungen am Capability-Status (z. B. Regel auf capset), korreliere Events mit Deployments und setze Alarme, wenn plötzlich starke Rechte auftauchen. Für knifflige Fälle hilft mir strace -e capget,capset, um Rechtemanipulationen sichtbar zu machen.

Systemd- und Container-Praxisbeispiele

Viele Dienste betreibe ich als systemd-Units und kapsle Rechte dort:

  • CapabilityBoundingSet=CAP_NET_BIND_SERVICE reduziert das erreichbare Rechtefenster auf das Nötigste.
  • AmbientCapabilities=CAP_NET_BIND_SERVICE gibt dem Dienst das Recht, ohne File-Capabilities an 80/443 zu binden.
  • NoNewPrivileges=yes verhindert spätere Rechtserweiterungen.
  • User=, Group=, ProtectSystem=strict, PrivateTmp=yes runden die Isolation ab.

In Containern starte ich Prozesse so minimal wie möglich: docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE --read-only. Für kurzlebige Jobs nutze ich Laufzeit-Capabilities statt File-Capabilities im Image, damit Builds reproduzierbar bleiben und Rechte an die Umgebung gebunden sind.

Konkrete Migrationen aus der Praxis

  • ping ohne Set-UID: Statt setuid root setze ich setcap 'cap_net_raw=+ep' /bin/ping. Damit kann jeder Nutzer ICMP-Sockets öffnen, ohne volle Root-Rechte. Ich prüfe regelmäßig mit getcap /bin/ping, ob das Attribut erhalten ist.
  • Webdienst an Port 80/443: Ich lasse meinen Dienst als unprivilegierten Nutzer laufen und gebe nur cap_net_bind_service. Falls der Dienst ohnehin vor einem Reverse Proxy hängt, kann ich alternativ auch dort an 80/443 binden und intern einen High-Port nutzen – ganz ohne zusätzliche Fähigkeiten.
  • Rechtewechsel im Prozess: Für Tools, die kurz erhöhte Rechte benötigen (z. B. Setzen von Nice-Levels), setze ich cap_sys_nice, erledige die Aktion früh und droppe die Fähigkeit anschließend wieder. Dauerhaft erhöhte Rechte meide ich.

Grenzen und Alternativen

Nicht jeder Anwendungsfall erfordert Capabilities. Oft existieren sichere Alternativen mit geringerem Risiko:

  • Socket Activation: Der Init-Dienst (z. B. systemd) öffnet privilegierte Sockets und übergibt sie an den Prozess. Mein Dienst braucht dann keine Bind-Rechte.
  • Port-Weiterleitung: Mit Firewall-Regeln leite ich 80/443 auf einen High-Port um. Der Dienst bleibt unprivilegiert, das Systemverhalten ändert sich nicht.
  • Unprivilegierte Low-Ports: Wo es passt, kann ich die Schwelle für unprivilegierte Ports anheben. Das weitet aber den Spielraum für alle Prozesse aus – ich wäge Risiko und Komfort sorgfältig ab.
  • Kleine Helfer statt Alleskönner: Lieber ein winziges, auditiertes Binary mit genau einer Fähigkeit als ein großer Monolith mit breitem Rechtekatalog.

Kurz zusammengefasst

Mit Linux Capabilities zerteile ich Root-Macht in kleine, gut steuerbare Privilegien. File Capabilities ersetzen riskante Set-UID-Binaries und reduzieren Folgen eines Angriffs. In Kombination mit engen sudo-Regeln, Rollen und MAC-Profilen entsteht Schichtschutz mit klaren Grenzen. Bounding- und Inheritable-Sets schränken Vererbungen ein und halten Prozesse auf Kurs. Wer so vorgeht, senkt Angriffsflächen spürbar und hält Verwaltungsaufwand überschaubar.

Aktuelle Artikel