...

Linux Capabilities statt Root: Minimalprinzip für Serverdienste

Ich setze linux capabilities ein, um Serverdienste nach dem Minimalprinzip zu betreiben und so nur die absolut nötigen Teilrechte zu vergeben. Dadurch reduziere ich die Angriffsfläche spürbar, ohne Funktionen zu blockieren.

Zentrale Punkte

  • Least-Privilege konsequent: Dienste erhalten nur exakt benötigte Fähigkeiten.
  • Feingranular statt Root: Rund 40–50 Capabilities ersetzen Vollzugriff.
  • Trennung von Prozessen: Privilege Separation mindert Schaden bei Exploits.
  • File Capabilities: Rechte direkt an Binärdateien binden.
  • Auditierbar: getcap liefert klare Sicht auf Sonderrechte.

Warum Root riskant ist – und wie Capabilities das ändern

Früher lief fast jeder Serverdienst mit Root-Rechten, was bei einer Kompromittierung sofort zur Systemübernahme führen konnte. Heute teile ich Rechte gezielt auf, indem ich Capabilities wie CAP_NET_BIND_SERVICE für Ports unter 1024 vergebe und alle anderen mächtigen Rechte entferne. So darf der Webserver binden, aber keine Kernelmodule laden oder Dateibesitzer ändern, was die Sicherheit deutlich anhebt. Eine klare Trennung der Aufgaben macht Angriffe weniger wirkungsvoll, weil ein ausgenutzter Prozess nur begrenzte Aktionen durchführen darf. Wer mehr Struktur in dieses Konzept bringen will, kann Rechte sehr fein granular aufteilen und so kritische Operationen systematisch einschränken. Aus einem monolithischen Root-Dienst entsteht damit ein Set aus Diensten mit kleinsten, klar definierten Befugnissen.

So funktionieren Capability-Sets im Kernel

Jeder Prozess besitzt mehrere Capability-Sets, die der Kernel bei sensiblen Aktionen prüft. Das Effective-Set entscheidet, was ein Prozess jetzt sofort darf, während das Permitted-Set den Pool möglicher Rechte enthält. Über das Inheritable-Set kann ich steuern, was beim execve() an Kindprozesse übergeht, was gerade bei Wrappern und Start-Skripten zählt. Das Bounding-Set definiert eine harte Obergrenze, sodass bestimmte Fähigkeiten nie wieder erlangt werden können, selbst bei Fehlern in der Anwendung. Mit dem Ambient-Set gebe ich Capabilities ohne SUID an reguläre Programme weiter und halte den Angriffsweg klein. Diese Sets zusammen erlauben mir eine sehr feine Steuerung, die weit über das klassische Alles-oder-Nichts von UID 0 hinausgeht.

Set Zweck Typische Nutzung Risiko bei Fehlkonfiguration
Effective Jetzt wirksame Fähigkeiten Prüfung jeder privilegierten Operation Prozess kann sofort zu viel
Permitted Pool erlaubter Fähigkeiten Quelle für das Effective-Set Unnötige Reserven bleiben greifbar
Inheritable Vererbbare Fähigkeiten Kontrollierte Weitergabe bei execve() Kinder erben Rechte unnötig
Bounding Obere Grenze aller Rechte Dauerhafte Ausschlüsse definieren Zurückholen mächtiger Rechte möglich
Ambient Weitergabe ohne SUID Reguläre Programme erhalten Capabilities Breitere, stille Rechtevergabe

Wichtig in der Praxis sind zwei zusätzliche Aspekte: Erstens entscheiden Securebits darüber, ob ein Prozess nach einem Wechsel der Benutzer-ID (z. B. via setuid()) seine Capabilities behält. Mit PR_SET_KEEPCAPS lässt sich das gezielt steuern – typischer Ablauf: kurzzeitig als Root starten, benötigte Sockets oder Ressourcen anlegen, UID auf einen unprivilegierten Nutzer wechseln und nur die erforderlichen Capabilities weiterführen. Zweitens gilt: Das Bounding-Set ist endgültig in der aktuellen Prozesslinie. Wer hier früh im Startpfad überflüssige Fähigkeiten entfernt, kann später selbst durch Fehlkonfigurationen keine „verbotenen“ Rechte mehr erlangen.

Dateirechte mit File Capabilities steuern

Statt einem Dienst dauerhafte Sonderrechte einzuräumen, binde ich sie lieber direkt an die Binärdatei. Über setcap cap_net_bind_service=+eip /usr/bin/node erlaube ich Port-Bindings, ohne dass der Prozess als Root laufen muss. Mit getcap /usr/bin/node oder rekursiv getcap -r / 2>/dev/null prüfe ich die Vergabe und behalte die Kontrolle. Entfernen klappt über setcap -r /pfad/zum/binary, sodass ich temporäre Rechte nach dem Einsatz wieder zurücknehme. Beim Kopieren gehen Capabilities häufig verloren, daher sichere ich sie im Deployment explizit, um Regressions zu vermeiden. So bleiben Builds reproduzierbar und Rechte bleiben stets nachvollziehbar dokumentiert.

File Capabilities leben als Extended Attribute (security.capability) auf dem Dateisystem. Das setzt ein fähiges FS und passende Mount-Optionen voraus. Tools wie tar und rsync müssen XAttrs explizit mitnehmen (z. B. tar --xattrs, rsync -XA), sonst verschwinden Rechte stillschweigend. Paketmanager können Capabilities in Post-Install-Schritten setzen; ich halte das bevorzugt im Build/Release-Prozess fest, um Überraschungen bei Upgrades zu vermeiden. Kritisch ist außerdem: Interpreter-Skripte (z. B. mit Shebang) erben File Capabilities nicht wie ELF-Binaries. Mächtige Fähigkeiten auf Interpreter zu legen, ist ohnehin riskant – ich entkoppele lieber und arbeite mit dedizierten, kleinen Helfer-Binaries.

Minimalprinzip für Serverdienste in der Praxis

Ich starte Webserver als unprivilegierten Nutzer und erteile ausschließlich CAP_NET_BIND_SERVICE, damit der Prozess an 80/443 binden darf und keine weiteren Privilegien mitbringt. Dateien und Verzeichnisse steuere ich weiter über POSIX-Rechte sowie optional MAC-Profile, wodurch Konfiguration und Inhalte getrennt abgesichert bleiben. Monitoring- oder Logging-Agents erhalten gezielte Netzwerkrechte und Leserechte auf Logs, aber keinerlei Befugnisse für Systemänderungen. In Container-Umgebungen reduziere ich den Capability-Satz zusätzlich und kombiniere ihn mit Systemcall-Filtern, um das Verhalten eng zu halten. Diese Kombination senkt die Wirkung erfolgreicher Exploits und erhöht die Transparenz der tatsächlichen Befugnisse. Dienste bleiben funktionsfähig, doch der Handlungsspielraum bleibt klein.

Statt Capabilities zu vergeben, eliminiere ich sie manchmal ganz: Socket-Aktivierung stellt privilegierte Listener (z. B. 443/tcp) über den Init-Prozess bereit und übergibt nur den offenen Datei-Deskriptor an den Dienst. Der Applikationsprozess benötigt dann kein CAP_NET_BIND_SERVICE mehr. Ebenso lassen sich einmalige Root-Aktionen (z. B. PID-Verzeichnis anlegen) vorab erledigen und danach konsequent Rechte abgeben. Je weniger Fähigkeiten überhaupt im Spiel sind, desto robuster bleibt das System gegen Kettenfehler.

Privilege Separation sauber umsetzen

Ich zerlege umfangreiche Dienste in mehrere Teilprozesse, die jeweils nur die nötigen Capabilities tragen. Ein Frontend-Prozess terminiert TLS und bindet an Ports, besitzt aber keine Dateisystemrechte für kritische Änderungen. Ein Backend-Prozess verarbeitet Daten intern, hat minimale Leserechte auf Konfiguration und spricht mit Datenbanken ohne eigene Netzwerkfähigkeiten. Administrative Jobs wie Logrotation oder Wartung laufen über dedizierte Tools mit zeitlich begrenzten Fähigkeiten. Greift ein Angreifer einen Teil an, bleibt der Rest des Systems unbehelligt, weil die Berechtigungen eng definiert sind. So skaliert Sicherheit mit der Struktur der Anwendung statt mit allmächtigen Systemrechten.

Für diese Aufteilung bietet sich eine klare Start-Orchestrierung an. In klassischen Setups erledigt das ein Supervisor; auf heutigen Systemen nutze ich bevorzugt systemd, weil es Capabilities, cgroups und Namespaces direkt integriert. So kann ich Netz-Frontend, Worker und Admin-Tools jeweils mit eigener Sandbox starten, Ressourcen begrenzen und im Fehlerfall automatisch neu starten lassen – ohne jemals pauschal Root-Rechte zu verteilen.

Security Controls kombinieren: POSIX, MAC und Capabilities

Capabilities wirken am besten, wenn ich sie mit klassischen Dateirechten und MAC-Systemen kombiniere. SELinux oder AppArmor können Aktionen trotz vergebener Capabilities weiter einschränken und damit Mehrfachschutz erzeugen. So darf ein Prozess zwar an einen Port binden, wird aber durch eine Policy am Lesen sensibler Dateien gehindert. Wer den Unterschied der Ansätze vertiefen will, findet einen klaren Vergleich in SELinux vs. AppArmor und kann dann eine passende Policy-Strategie wählen. In Summe entsteht ein Verteidigungsverbund, der Angriffe auf mehreren Ebenen stoppt und die Angriffsfläche weiter verkleinert. Rechtevergabe bleibt damit prüfbar, wiederholbar und konsistent.

Besonders restriktiv wird es, wenn ich zusätzlich NoNewPrivileges aktiviere: Prozesse und Kinder dürfen dann keine neuen Privilegien erlangen (z. B. über SUID oder neu gesetzte File Capabilities). Zusammen mit einer harten Capability-Bounding-Liste entsteht eine Sicherheitsbarriere, die selbst bei fehlerhafter Konfiguration spätere Privilegaufweitung verhindert.

Capabilities sicher verteilen und auditieren

Ich halte den zugewiesenen Fähigkeitensatz so klein wie möglich und vermeide alles, was nach „zweitem Root“ klingt, etwa CAP_SYS_ADMIN. Interpreter wie Python, Perl oder Shells erhalten keine mächtigen Capabilities, weil ihr Funktionsumfang leicht missbraucht werden kann. Mit regelmäßigen Audits über getcap -r / 2>/dev/null spüre ich Ausreißer auf und räume auf. Binärdateien mit Capabilities liegen schreibgeschützt, gehören Root und befinden sich nicht in Pfaden, die normale Nutzer verändern dürfen. Zusätzlich prüfe ich eigene Binaries vor jedem Release und dokumentiere Änderungen, damit Review und Reproduktion zuverlässig klappen. So bleibt die Rechtevergabe beherrschbar und Anpassungen bleiben nachvollziehbar.

Zur Laufzeit prüfe ich Prozesse über /proc/<pid>/status (Felder CapEff, CapPrm, CapInh). Das liefert die Hex-Werte der aktiven Sets und zeigt sofort, ob eine Anwendung mehr kann als vorgesehen. Werkzeuge wie capsh --print oder getpcaps <pid> erleichtern das Debugging. Mit dem Linux-Audit-Subsystem protokolliere ich zusätzlich Änderungen an Capabilities oder an security.capability-Attributen von Dateien, um Manipulationen nachzuverfolgen. Wer Capabilities als Konfigurationsobjekt behandelt und Änderungen streng reviewt, macht Audits reproduzierbar und vereinfacht Compliance-Nachweise.

Häufige Stolperfallen und wie ich sie vermeide

Eine typische Falle: Beim Kopieren gehen Attribute verloren, wodurch Dienste plötzlich nicht mehr starten oder umgekehrt zu wenig eingeschränkt sind. Ich sichere Capabilities daher explizit im Build, oder ich vergebe sie im Post-Install-Schritt automatisiert. Ein weiterer Fehler ist die großzügige Nutzung von Allzweck-Capabilities, die mehr öffnen als nötig. Besser ist es, konkrete Fähigkeiten wie CAP_NET_RAW oder CAP_CHOWN nur dort einzusetzen, wo sie eine reale Funktion freischalten. Auch das Ambient-Set setze ich sparsam ein, damit sich keine ungewollte Weitergabe verbreitet. Wer gezielt reduziert und regelmäßig prüft, verhindert Sicherheitslücken durch Bedienfehler.

Ebenfalls wichtig: SUID-Binaries systematisch abschaffen. Wo früher SUID nötig war (z. B. ICMP senden), lässt sich oft mit CAP_NET_RAW arbeiten – oder, besser noch, die Funktion in einen kleinstmöglichen Helfer-Prozess mit sehr engem Pflichtenheft auslagern. Außerdem vermeide ich, Capabilities auf temporäre oder von Nutzern beschreibbare Pfade zu legen. Ein strenges Eigentümer- und Deploy-Regime (Root:root, 0755/0555, unveränderliche Pfade) verhindert Rechte-„Verlust“ durch ersetzte Binaries.

Capabilities in Containern und DevSecOps

In Container-Umgebungen reduziere ich die Capabilities aggressiv und entferne alles, was der Workload nicht zwingend braucht. Zusätzlich lege ich ein Seccomp-Profil an, das riskante Systemcalls sperrt und so eine weitere Hürde einzieht. In Build-Pipelines definiere ich Capabilities deklarativ, teste sie in Staging und erfasse sie versioniert. Compliance profitiert, weil ich das Least-Privilege-Prinzip nachweisen und Rechteänderungen lückenlos dokumentieren kann. So bleiben Container eng geführt, ohne ihre Aufgaben zu behindern, und die Angriffsfläche bleibt klein. In Kombination mit Images, die nur das Nötigste enthalten, steigt die Sicherheit zusätzlich.

Wichtig im Container-Kontext: Capabilities sind in Namespaces relativ. Innerhalb eines User-Namespaces kann ein Prozess zwar „Root“ sein, seine Fähigkeiten wirken aber nur auf die zugehörigen Namespaces – das senkt den Schadenradius deutlich. Im Gegenzug ist „--privileged“ praktisch immer tabu: Es schaltet die harte Bounding-Grenze ab und öffnet weit mehr als nötig. Ich starte Container daher standardmäßig mit „alles dropen, gezielt adden“ und ergänze NoNewPrivileges, cgroup-Limits und schreibgeschützte Mounts. Für Dienste, die nur lauschen müssen, nutze ich Socket-Aktivierung oder Sidecars, um ganz ohne zusätzliche Capabilities auszukommen.

Systemd-Beispiel: Capabilities deklarativ beschränken

In service-Units lege ich fest, was ein Prozess maximal darf – klar, wiederholbar und versionskontrollierbar. Ein kompaktes Beispiel für einen Webdienst, der nur an 443 binden darf und sonst stark eingeschränkt ist:

[Unit]
Description=Minimaler Webdienst ohne Root

[Service]
User=web
Group=web
ExecStart=/usr/bin/my-web
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
ReadWritePaths=/var/lib/my-web
RestrictAddressFamilies=AF_INET AF_INET6
SystemCallFilter=@basic-io @network-io
LockPersonality=yes
MemoryDenyWriteExecute=yes

[Install]
WantedBy=multi-user.target

Die Kombination aus AmbientCapabilities und einer harten CapabilityBoundingSet sorgt dafür, dass der Dienst nur die eine nötige Fähigkeit erhält und nichts darüber hinaus. NoNewPrivileges verhindert nachträgliche Privilegaufwertung, ProtectSystem und ReadWritePaths regeln den Schreibzugriff, und ein enger Systemcall-Filter unterbindet unnötige Kernel-Eintrittspunkte.

Häufig gebrauchte Capabilities – und sichere Alternativen

  • CAP_NET_BIND_SERVICE: Binden an Ports <1024. Alternative: Socket-Aktivierung, Reverse Proxy vorlagern.
  • CAP_NET_RAW: Rohsockets (Ping, DHCP). Alternative: kleiner Helferprozess statt breiter Interpreter-Rechte.
  • CAP_CHOWN/CAP_FOWNER: Eigentümer/ACL-Anpassungen. Alternative: vorbereitete Verzeichnisse, dedizierte Maintenance-Tools.
  • CAP_SYS_PTRACE: Debugging/Tracing – nur in Staging, nie breit in Produktion.
  • CAP_SYS_ADMIN: „Zweites Root“ – vermeiden; konkretisieren, was wirklich gebraucht wird.

Ich wähle stets die kleinste Menge, die die benötigte Funktion exakt freischaltet. Falls eine Capability mehrere Angriffswege öffnet (z. B. RAW-Sockets), kapsle ich die Funktion in einen getrennten, kurzlebigen Prozess und nehme Rechte nach getaner Arbeit wieder zurück.

Praxis-Checkliste für robuste Capabilities

  • Startet der Dienst ohne Root? Falls nein: Warum nicht – und lässt sich das mit Socket-Aktivierung oder kleinen Helfer-Binaries lösen?
  • Sind alle vergebenen Capabilities nachweislich erforderlich (Funktionsnachweis, Testfälle)?
  • Ist das Bounding-Set maximal eng und früh gesetzt?
  • Sind XAttrs im Build, bei Deployments und Backups konsistent erhalten (rsync/tar-Flags, Package-Skripte)?
  • Verzichte ich auf Capabilities bei Interpretern und SUID-Binaries konsequent?
  • Sind Besitzer- und Dateirechte (Root:root, 0755/0555) sowie Pfade gegen Austausch geschützt?
  • Greifen zusätzliche Kontrollen (NoNewPrivileges, Seccomp, MAC-Profile)?
  • Werden Prozess-Capabilities zur Laufzeit auditiert (/proc/<pid>/status, getpcaps) und Änderungen dokumentiert?
  • Sind Container standardmäßig mit „drop all, add minimal“ und ohne „privileged“ konfiguriert?

Kurz zusammengefasst

Linux Capabilities zerlegen klassische Root-Rechte in kleine, steuerbare Einheiten und setzen dadurch das Minimalprinzip technisch sauber um. Ich weise Diensten nur die Fähigkeiten zu, die sie wirklich benötigen, und kombiniere das mit POSIX-Rechten sowie MAC-Policies. File Capabilities sorgen dafür, dass Rechte direkt an Binaries hängen und Audits klar zeigen, wer was darf. Mit Privilege Separation, reduzierten Container-Rechten und Systemcall-Filtern begrenze ich Schäden, falls eine Schwachstelle greift. Regelmäßige Prüfungen, strenge Eigentümer- und Schreibrechte und ein dokumentierter Release-Prozess halten die Vergabe schlank. So bleibt der Serverdienst funktionsfähig, doch der Spielraum für Angreifer wird konsequent klein gehalten.

Aktuelle Artikel

Serverraum mit Linux-Servern und Warnsymbol für GhostLock Kernel-Sicherheitslücke
Sicherheit

GhostLock CVE – Technische Analyse der Linux Kernel-Schwachstelle

GhostLock CVE-2026-43499 ist eine kritische Use-after-free-Schwachstelle im Linux-Kernel. In dieser Analyse zur GhostLock CVE zeigen wir den Exploit-Chain zur Root-Eskalation und geben konkrete Security-Empfehlungen für Administratoren.