Zum Inhalt springen
Categoria: Härtung8 Min. Lesezeit

SELinux ohne Angst: Eigene Policies fur kritische Dienste

Por Lucas Andrade ·

Vom Audit mit audit2allow zu versionierten Policy-Modulen, die in Produktion gepflegt werden, ohne dauerhaft im Permissive-Modus zu landen.

SELinux ohne Angst: Eigene Policies fur kritische Dienste

Jedes Mal, wenn ein Dienst auf RHEL oder Rocky kaputtgeht, ist der Reflex des Bereitschaftsteams derselbe: setenforce 0, Problem geloest, Ticket zu. Sechs Monate spaeter laeuft der gesamte Cluster im Permissive-Modus, niemand erinnert sich warum, und der Compliance-Bericht wird zu Science-Fiction. Das Basilisk OffSec Team hat zwei Jahre damit verbracht, solche Umgebungen in autorisierten Red Teams zu uebernehmen, und die Schlussfolgerung ist unbequem: ein deaktiviertes SELinux ist einer der zuverlaessigsten Wege von einer einzelnen RCE zu vollstandiger Kompromittierung. Dieser Beitrag zeigt, wie du eigene Policies fuer kritische Dienste schreibst, ohne die Produktion zu zerlegen, und warum setenforce 0 keine Loesung, sondern eine aufgeschobene Rechnung ist. Wir arbeiten den Weg von der Diagnose eines Denials ueber das saubere Einfangen der AVCs bis zur reviewten, signierten und ueber Ansible ausgerollten Policy durch, immer mit dem Ziel, den Host in Enforcing zu halten.

Enforcing statt permissive: warum es zaehlt

SELinux ist Mandatory Access Control: Selbst wenn ein Prozess als root laeuft, begrenzt die Policy, welche Typen er lesen, schreiben und ausfuehren darf. Genau das bricht eine Exploit-Kette, denn ein kompromittierter httpd_t darf eben nicht shadow_t lesen oder in bin_t schreiben. Permissive protokolliert nur, es blockiert nichts, also ist ein Cluster im Permissive-Modus funktional ungeschuetzt. Der Zielzustand ist immer Enforcing, verifizierbar mit getenforce und sestatus. Ein sauber gepflegter Enforcing-Host ist kein Hindernis fuer den Betrieb, sondern die letzte Perimeter-Verteidigung, wenn eine Anwendungsschwachstelle ausgenutzt wird; das ergaenzt die Basisarbeit aus Linux-Server-Hardening: CIS Benchmark Anwenden Ohne die Produktion zu Zerlegen.

Bestehende Policy lesen, bevor du schreibst

Bevor du irgendeine Policy schreibst, musst du lesen, was bereits existiert. Der Befehl seinfo -t listet rund 5.000 Typen auf RHEL 9 Standard, und sesearch --allow -s httpd_t zeigt genau, was Apache anfassen darf. seinfo -ahttpd_t -x loest die Attribute einer Domain auf, sesearch --allow -s httpd_t -t etc_t -c file beantwortet gezielt, ob ein Zugriff bereits erlaubt ist. Wir beginnen jede Untersuchung mit diesen Abfragen, denn in 80% der Faelle existiert bereits ein passender Typ oder Boolean, und du brauchst gar keine neue Policy zu schreiben, sondern nur das Label oder den Schalter zu korrigieren.

AVCs sauber einfangen

Wenn wirklich etwas fehlt, faengst du die Denials ein, statt zu raten. Starte den Dienst mit ausearch -m AVC -ts recent parallel und setze die betroffene Domain in den VORUEBERGEHENDEN Permissive-Modus, niemals das ganze System: semanage permissive -a httpd_t. So laeuft nur dieser eine Dienst ungehindert und protokolliert saemtliche Verstoesse, waehrend der Rest des Systems Enforcing bleibt. Reproduziere den kompletten Anwendungsfall (Start, Reload, alle Codepfade), sammle die AVCs und entferne die Ausnahme sofort danach mit semanage permissive -d httpd_t. Ein vergessener Permissive-Eintrag ist genauso gefaehrlich wie ein global deaktiviertes SELinux.

audit2allow ist zweischneidig

audit2allow ist ein zweischneidiges Messer. ausearch -m AVC | audit2allow -M meinmodul erzeugt ein .te, das kompiliert und funktioniert, aber haeufig absurde Rechte vergibt wie allow httpd_t shadow_t:file read. Unsere interne Checkliste verlangt, dass jedes .te eine manuelle Review durchlaeuft, bevor semodule -i laeuft. Suche nach Regeln, die shadow_t, etc_t, kernel_t oder self:capability sys_admin beruehren, das sind rote Flaggen. Ein Denial zu erlauben, weil der Dienst sonst nicht startet, ist bequem und oft der Beginn genau der Luecke, die ein Angreifer spaeter braucht. Frag bei jeder Regel: warum will der Prozess das, und ist der Zugriff wirklich notwendig?

Policy von Grund auf mit refpolicy

Fuer neue Dienste schreiben wir die Policy lieber von Grund auf mit der Makro-Sprache von refpolicy. Ein typisches Modul besteht aus drei Dateien: meinservice.te mit den Regeln, meinservice.fc mit den File-Contexts und meinservice.if mit Interfaces fuer andere Domains. make -f /usr/share/selinux/devel/Makefile erzeugt das .pp, das du mit semodule -i installierst. Wir versionieren diese drei Dateien im git neben Ansible, und jeder PR durchlaeuft dieselbe Review wie Anwendungscode. So bleibt die Policy nachvollziehbar, reproduzierbar und pruefbar, statt als undokumentierte Handarbeit auf einem einzelnen Host zu verrotten.

Ports und File-Contexts: 80% der Faelle

Dienste, die Sockets auf nicht standardisierten Ports oeffnen, sind der haeufigste Fall von stillem Bruch. Postgres auf 5433 zum Beispiel braucht semanage port -a -t postgresql_port_t -p tcp 5433, keine neue Policy. Nginx, das Dateien ausserhalb von /var/www ausliefert, will semanage fcontext -a -t httpd_sys_content_t "/srv/app(/.*)?" gefolgt von restorecon -Rv /srv/app. Achtzig Prozent der Faelle, die wir sehen, sind Label- und Port-Probleme, keine fehlenden Allow-Regeln. Pruefe daher immer zuerst mit ls -Z und semanage port -l, ob ein falsches Label oder ein nicht registrierter Port die Ursache ist, bevor du auch nur an ein .te denkst.

Booleans statt Custom-Policy

Viele scheinbar komplexe Anforderungen sind bereits als Boolean vorgesehen. getsebool -a | grep httpd zeigt Dutzende Schalter; httpd_can_network_connect erlaubt ausgehende Verbindungen, httpd_can_network_connect_db nur zur Datenbank. Setze sie persistent mit setsebool -P httpd_can_network_connect_db on. Ein Boolean ist einem Custom-Modul immer vorzuziehen, weil er von den Distributionspflegern gewartet, dokumentiert und beim Update mitgezogen wird. Custom-Policy ist der letzte Ausweg, nicht der erste Griff; wer zuerst nach Booleans sucht, schreibt in der Praxis deutlich weniger eigene .te-Dateien. Dokumentiere jeden gesetzten Boolean in deinem Config-Management, denn ein manuell gesetzter, nicht versionierter Schalter ist die naechste Quelle von Policy-Drift und faellt bei einem Rebuild des Hosts stillschweigend weg.

Confined vs. unconfined: der haeufige Trugschluss

Ein weit verbreiteter Irrtum ist zu glauben, ein Dienst sei geschuetzt, nur weil SELinux Enforcing ist. Viele selbst gestartete Prozesse laufen als unconfined_service_t oder init_t und sind damit praktisch ungebremst. Pruefe mit ps -eZ | grep meinservice, in welcher Domain der Dienst wirklich laeuft. Ein Binary unter /usr/local/bin traegt oft bin_t statt einer eigenen Domain, wodurch kein Transition stattfindet. Der ganze Aufwand einer Custom-Policy ist wertlos, wenn der Prozess nie in die confined Domain transitioniert; genau dafuer existiert die .fc-Datei plus ein type_transition vom startenden init_t. Verifiziere die Transition immer, statt sie anzunehmen.

Testen und Zurueckrollen

Eine Policy ist Code und wird wie Code getestet. Installiere zuerst in Staging mit semodule -i meinservice.pp, fahre den vollstaendigen Anwendungsfall und pruefe ausearch -m AVC -ts recent auf null neue Denials. Liste geladene Module mit semodule -l, entferne ein fehlerhaftes sofort mit semodule -r meinservice. Halte den Prioritaeten-Mechanismus im Kopf: semodule -X 400 -i laedt mit hoeherer Prioritaet und ueberschreibt die Distributions-Version kontrolliert. Fuer schnelles Iterieren setzt du die Ziel-Domain kurz auf permissive, sammelst die verbleibenden AVCs in einem Durchlauf und ergaenzt sie begruendet, statt in zehn Runden Deploy-und-Bete zu verfallen.

Observability und Policy-Drift

Wartung in Produktion verlangt Observability. Wir konfigurieren setroubleshoot-server im Silent-Modus, der AVCs ueber journald an das SIEM weiterleitet, mit Sigma-Regeln, die auf unerwartete Denials abgestimmt sind. Wenn ein Deploy bricht, kommt der Alarm, bevor der User sich beschwert. Wir lassen ausserdem sealert -a /var/log/audit/audit.log woechentlich im Staging laufen, um Policy-Drift abzufangen, bevor er in die Produktion gelangt. Fuer die forensische Seite, wenn ein Denial auf einen echten Angriff hindeutet, schliesst DFIR unter Linux: Live-Triage mit UAC und Velociraptor an, und die signierte Policy passt in die Kette aus Supply Chain Security: Sigstore-Signatur und echte SBOMs in CI/CD.

Fallstricke

Die haeufigsten Fallstricke: setenforce 0 als Dauerzustand statt als 15-Minuten-Diagnose; chcon statt semanage fcontext plus restorecon benutzen, wodurch das Label beim naechsten restorecon oder Relabel verloren geht; audit2allow-Ausgabe blind einspielen; und dontaudit-Regeln vergessen, die relevante Denials verstecken (temporaer abschaltbar mit semodule -DB). Ein weiterer Klassiker ist ein Label, das bei einem tar-Restore verloren geht; nutze tar --selinux oder relabele danach explizit. Recon-Basis fuer die Angreiferperspektive liefert Nmap Fortgeschritten: NSE Skripte fur internes Recon im simulierten Firmenlab.

Checkliste

Bevor eine Custom-Policy in Produktion geht: (1) zuerst nach passendem Typ und Boolean gesucht; (2) AVCs nur im per-Domain-Permissive eingefangen, System blieb Enforcing; (3) jedes .te manuell reviewed, keine shadow_t/sys_admin-Regel ohne Begruendung; (4) File-Contexts ueber semanage fcontext plus restorecon, nie chcon; (5) Ports registriert; (6) drei Dateien im git versioniert; (7) .pp signiert und ueber Ansible deployt; (8) SIEM-Alert auf unerwartete Denials aktiv; (9) Enforcing per getenforce bestaetigt; (10) kein Permissive-Eintrag zurueckgeblieben; (11) die Prozess-Domain via ps -eZ als confined bestaetigt, nicht als unconfined_service_t.

FAQ

Warum nicht SELinux durch AppArmor ersetzen? Auf RHEL-Familie ist SELinux der unterstuetzte, integrierte Weg; ein Wechsel wirft die Distributions-Policies weg. Kostet Enforcing Performance? Der Overhead ist im einstelligen Prozentbereich und in der Praxis vernachlaessigbar gegenueber dem Sicherheitsgewinn. Was tun bei einem Dienst, der partout nicht startet? Per-Domain permissive setzen, kompletten Ablauf reproduzieren, AVCs sammeln, Policy schreiben, reviewen, wieder Enforcing schalten, verifizieren. Niemals das System global auf permissive lassen.

Praktisches Fazit: lass setenforce 0 nie laenger als 15 Minuten in Produktion laufen. Nutze semanage permissive -a domain_t, um das Problem zu isolieren, fange AVCs mit ausearch ein, pruefe die audit2allow-Ausgabe manuell, committe das .te in git, signiere das .pp und deploye ueber Ansible. Wenn du nicht jede Allow-Regel im Code-Review rechtfertigen kannst, ist die Policy nicht bereit. SELinux ist kein Hindernis, sondern die letzte Perimeter-Verteidigung, die zwischen einer einzelnen Schwachstelle und dem Totalverlust des Hosts steht. Behandle jede Policy wie ein Produkt mit Lebenszyklus: dokumentiert, versioniert, getestet und bei jedem Distributions-Update erneut verifiziert, damit ein Upgrade die muehsam erarbeitete Confinement nicht stillschweigend aufweicht.

Related posts

Nenhum comentário ainda

Seja o primeiro a comentar.

Deixe seu comentário

Entre com sua conta Canverly para comentar. Você pode usar a mesma conta em qualquer site da rede.

Entrar com Canverly