ADMIN
2021
08
2021-08-01T12:00:00
Hochverfügbarkeit und Monitoring
PRAXIS
054
Open-Source-Tipp
Tools
Systeme mit SELinux absichern
Geschützte Bereiche
von Thorsten Scherf
Veröffentlicht in Ausgabe 08/2021 - PRAXIS
Schwachstellen treten immer wieder in den unterschiedlichsten Softwarekomponenten auf. Mit SELinux steht ein mächtiges Werkzeug zur Verfügung, um Schaden auf den betroffenen Systemen zu minimieren. Leider wird die Kernel-Erweiterung auf vielen Systemen aus Unwissenheit immer wieder abgeschaltet. Warum dies keine gute Idee ist, erklärt der Open-Source-Tipp in diesem Monat.
Fehlerhafter Programmcode kann schwerwiegende Folgen haben. Im schlimmsten Fall können Angreifer ganze Systeme übernehmen, ohne hierfür einen lokalen Zugang zu diesen Systemen zu benötigen. Möglich wird dies durch Softwarefehler, die eine Remote Code Execution (RCE) erlauben, also das Ausführen von Schadsoftware aus dem Netzwerk heraus. Wer lokalen Zugang zu Systemen besitzt, auf denen fehlerhafte Software installiert ist, dem eröffnen sich noch wesentlich weitreichendere Möglichkeiten, um Softwarefehler auszunutzen.
Schwachstellen in runC-Container-Runtime
Erst vor kurzem wurde im CVE-2021-30465 [1] ein Fehler in der Software runC [2] beschrieben. Die Software stellt eine Container-Runtime zur Verfügung, die auf Basis der Open-Container-Initiative-Spezifikation (OCI) Container verwaltet. Alle bekannten Container-Manager wie beispielsweise Docker, Podman oder CRI-O verwenden diese Runtime und setzen runC ein, meistens nicht direkt sichtbar für den Anwender. Ein Fehler in runC, der mittlerweile behoben ist, erlaubte einen sogenannten "symlink exchange"-Angriff, bei dem Angreifer das Dateisystem des Container-Hosts innerhalb eines beliebigen Containers, der von runC gestartet wurde, einbinden konnten.
Bereits vor zwei Jahren tauchte mit CVE-2019-5736 [3] ein Fehler für diese Software auf. Dieser hatte sogar noch schwerwiegendere Konsequenzen, da hiermit Container-Prozesse aus dem Container "ausbrechen" und beliebigen Programmcode auf dem Container-Host ausführen konnten. Somit war es Angreifern sogar möglich, root-Rechte auf dem Host zu erlangen, wenn der Container als root lief.
Fehlerhafter Programmcode kann schwerwiegende Folgen haben. Im schlimmsten Fall können Angreifer ganze Systeme übernehmen, ohne hierfür einen lokalen Zugang zu diesen Systemen zu benötigen. Möglich wird dies durch Softwarefehler, die eine Remote Code Execution (RCE) erlauben, also das Ausführen von Schadsoftware aus dem Netzwerk heraus. Wer lokalen Zugang zu Systemen besitzt, auf denen fehlerhafte Software installiert ist, dem eröffnen sich noch wesentlich weitreichendere Möglichkeiten, um Softwarefehler auszunutzen.
Schwachstellen in runC-Container-Runtime
Erst vor kurzem wurde im CVE-2021-30465 [1] ein Fehler in der Software runC [2] beschrieben. Die Software stellt eine Container-Runtime zur Verfügung, die auf Basis der Open-Container-Initiative-Spezifikation (OCI) Container verwaltet. Alle bekannten Container-Manager wie beispielsweise Docker, Podman oder CRI-O verwenden diese Runtime und setzen runC ein, meistens nicht direkt sichtbar für den Anwender. Ein Fehler in runC, der mittlerweile behoben ist, erlaubte einen sogenannten "symlink exchange"-Angriff, bei dem Angreifer das Dateisystem des Container-Hosts innerhalb eines beliebigen Containers, der von runC gestartet wurde, einbinden konnten.
Bereits vor zwei Jahren tauchte mit CVE-2019-5736 [3] ein Fehler für diese Software auf. Dieser hatte sogar noch schwerwiegendere Konsequenzen, da hiermit Container-Prozesse aus dem Container "ausbrechen" und beliebigen Programmcode auf dem Container-Host ausführen konnten. Somit war es Angreifern sogar möglich, root-Rechte auf dem Host zu erlangen, wenn der Container als root lief.
Solche und ähnliche Fehler passieren leider immer wieder. Die gute Nachricht ist jedoch, dass Linux von Haus aus ein mächtiges Sicherheitswerkzeug mitbringt, mit dem sich solche Fehler entweder komplett oder zumindest teilweise verhindern lassen. Im Folgenden erklären wir am Beispiel von CVE-2019-5736, wie dieser Schutz im Detail funktioniert.
SELinux-Grundlagen
Zuvor ist es allerdings notwendig, ein gewisses Grundverständnis über die generelle Funktionsweise von SELinux zu erlangen. Das Sicherheits-Framework basiert auf der sogenannten Mandatory-Access-Control (MAC). Anderes als bei der klassischen Rechteverwaltung auf POSIX-Systemen, bei denen die Discretionary-Access-Control (DAC) zum Einsatz kommt, muss jeder Zugriff auf ein Objekt explizit erlaubt werden. Dies findet zumeist in einem Regelwerk statt, in dem der Zugang von Prozessen oder Benutzern zu unterschiedlichen Objekten, wie beispielsweise Dateien, definiert ist. Taucht ein Zugriff in diesem Regelwerk nicht auf, so ist dieser auch nicht erlaubt und wird bereits auf Kernel-Ebene unterbunden. MAC-Systeme wie SELinux verfügen im Vergleich zu DAC-Systemen über eine sehr fein granulierte Zugriffssteuerung, weshalb dort neben dem klassischen Lesen, Schreiben und Ausführen noch viele weitere Rechte existieren.
SELinux verwendet eine Abstraktionsebene, um Prozesse, Benutzer und Objekte in unterschiedliche Kategorien zu unterteilen. So bekommen Prozesse und User eine Domäne zugeordnet, in der diese ablaufen beziehungsweise interagieren. Objekte hingegen sind mithilfe einer bestimmten Typen-Klasse definiert. Im SELinux-Regelwerk ist dann festgelegt, mit welchen Rechten Prozesse oder Benutzer aus einer bestimmten Domäne auf bestimmte Typen, und somit die damit verbundenen Objekte, zugreifen dürfen.
Schutz vor fehlerhafter Software
Mit diesem Grundlagenwissen sollte Sie nun besser verstehen, wieso SELinux oftmals hilft, die negativen Auswirkungen von Softwarefehlern zu minimieren und Angriffe hierüber abzuwehren. Im folgenden Beispiel schauen wir uns das Problem aus dem CVE-2019-5736 etwas näher an. In diesem ist beschrieben, wie Angreifer mit präparierten Container-Images das Binary "/usr/bin/runC" über die Datei "/proc/self/exe" überschreiben können, um so beim nächsten Aufruf von runC Schadsoftware zu starten um damit dann das komplette System zu übernehmen.
Dazu muss bekannt sein, dass "/proc/ self/exe" ein symbolischer Link ist, der immer auf das aktuell ausgeführte Programm zeigt. Rufen Sie also beispielsweise ls auf, um sich den Symlink anzeigen zu lassen, zeigt dieser natürlich auch auf das ls-Binary, da dies das Programm ist, das Sie zuletzt aufgerufen haben:
ls -l /proc/self/exe
lrwxrwxrwx. 1 tscherf tscherf 0 May 27 11:25 /proc/self/exe -undgt; /usr/bin/ls
Genauso verhält es sich auch, wenn Sie runC verwenden, um einen Container zu starten.
Labels und Domänen
Das runC-Binary verwendet das Typen-Label "container_runtime_exec_t":
ls -Z /usr/bin/runc
system_u:object_r:container_runtime_exec_t:s0 /usr/bin/runc
Nutzen Sie nun also beispielsweise podman, um mithilfe von runC eine bash-Shell in einem Container zu starten, läuft dieser Prozess innerhalb der Domäne "container_t", wie das folgende Beispiel zeigt:
podman run -it fedora:latest /bin/bash
ps -eZ|grep bash
system_u:system_r:container_t:s0:c195,c728 2349737 pts/0 00:00:00 bash
Um nun zu schauen, ob der bash-Prozess aus der Domäne "container_t" Zugriff auf das runC-Binary vom Typ "container_runtime_exec_t" besitzt, können Sie das Tool "sesearch" wie folgt aufrufen:
sesearch --allow --source container_t --target container_runtime_exec_t --class file -p write
Sie erhalten kein Ergebnis zurückgeliefert. Da auf einem SELinux-System jeder Zugriff explizit erlaubt sein muss, wird der Versuch, die Datei "/usr/bin/runc" durch einen Container-Prozess zu überschreiben, also erfolgreich unterbunden. Das Sicherheitsproblem, das im CVE-2019-5736 beschrieben ist, wurde somit durch den Einsatz von SELinux erfolgreich abgewehrt und Angreifer sind somit nicht in der Lage, Schadcode durch das Überschreiben der Datei "/usr/ bin/runc" auf dem Container-Host-System auszuführen.
Sollte es Sie interessieren, auf welche Dateien Container-Prozesse denn nun überhaupt zugreifen dürfen, können Sie hierfür erneut das Tool "sesearch" verwenden, indem Sie beim Aufruf die Target-Option einfach weglassen:
sesearch --allow --source container_t --class file -p write
allow container_t container_t:file { append getattr ioctl lock open read write };
Hiermit bekommen Sie nun angzeigt, dass Prozesse aus der Domäne "container_t" – diese Domäne wird von allen Container-Prozessen verwendet – auf Dateiobjekte (file) lediglich dann schreibend zugreifen dürfen, wenn diese über den SELinux-Typen "container_file_t" verfügen. Die runC-Binary-Datei verwendet diesen Typen jedoch nicht, weshalb SELinux schreibende Zugriffe von Container-Prozessen auf diese Datei unterbindet.
SELinux-Status verifizieren
Das SELinux-Framework aktivieren Sie über die Konfigurationsdatei "/etc/selinux/config". Eine Änderung an dieser Datei erfordert in jedem Fall einen Neustart des Systems:
grep '^SELINUX=' /etc/selinux/config
SELINUX=enforcing
Interaktiv können Sie den Status mithilfe von getenforce
abfragen und mit setenforce auch temporär ändern, solange sich das System lediglich im Permissive-Modus befindet. In diesem Modus wertet das System zwar die SELinux-Regeln aus und generiert Log-Einträge für nicht erlaubte Zugriffsversuche, verhindert diese aber nicht. Von daher bietet dieser Modus keinen zusätzlichen Schutz, sondern eignet sich lediglich zu Testzwecken bei der Entwicklung eigener SELinux-Regeln.
Fazit
Der Open-Source-Tipp hat aufgezeigt, dass SELinux ein integrales Sicherheitsframework ist, das von vielen Linux-Distributionen unterstützt wird und dort standardmäßig auch aktiv ist. Das Rahmenwerk hilft dabei, unterschiedliche Angriffe zu blockieren oder zumindest die Auswirkungen abzuschwächen.
Sie sollten daher überprüfen, ob SELinux auf den Systemen, die es unterstützen, auch tatsächlich aktiv ist. Wer nun auf den Geschmack gekommen ist und mehr über dieses Sicherheitsframework lernen möchte, dem ist für einen leichten Einstieg das Studium von zwei weiterführenden Publikationen [4 und 5] zu empfehlen.
(dr)