SELinux
Security Enhanced Linux (SELinux) ist eine Erweiterung des Linux-Kernels, die hauptsächlich von Red Hat und (interessanterweise) der NSA weiterentwickelt wird. Es sorgt auf Basis von Labeln und Regeln (den „Access Vectors“), dass Prozesse nur auf die Ressourcen Zugriff erhalten, die sie unmittelbar benötigen. Man kann sich SELinux also grob als eine Art Applikations-Firewall vorstellen. Es können jederzeit eigene Regeln definiert werden.
SELinux kennt drei verschiedene Betriebs-Modi:
- Enforcing
Im Standard aktiv. Die Regeln werden durch den Kernel angewendet. Regel-Anwendungen werden in
/var/log/audit/audit.log
protokolliert.- Permissive
Die Regeln werden durch den Kernel nicht angewendet, sie werden aber in
/var/log/audit/audit.log
protokolliert, als würden sie angewendet werden.- Disabled
SELinux ist komplett abgeschaltet.
Da SELinux ein Labeling-System ist, besitzt jeder Prozess, jede Datei, jedes Verzeichnis und jedes System-Objekt (Ports etc.) ein Label / den SELinux-Kontext. Die SELinux-Policies erlauben und definieren den Zugriff von gelabelten Prozessen auf gelabelte Objekte. Der Kernel sorgt für deren Einhaltung.
Die beiden wichtigsten Konzepte sind
Das Labeling (Dateien, Ports, Prozesse etc.)
Type Enforcement (also die Isolation von Prozessen auf Basis ihrer Labels).
Das korrekte Label-Format für einen Kontext ist user:role:type:level
.
Um SELinux temporär für das ganze System abzuschalten, setenforce 0
verwenden. Doch Vorsicht: das System ist von da an ungeschützt. Um SELinux zur Laufzeit auf „enforcing“ zu setzen , setenforce 1
nutzen.
So setzt man SELinux dauerhaft auf den Enforcing Mode (was der Standard-Einstellung in RHEL entspricht). Anschliessend ist ein Reboot fällig:
SELINUX=enforcing
Hinweis
Es gibt nur sehr wenige Ausnahmen, in denen man SELinux gänzlich abschalten sollte. Sehr wenige. Wirklich. Wer trotzdem darüber nachdenkt, weil man den Aufwand der Policy-Konfiguration scheut, sollte vorher probieren, einzelne Server-Prozesse in den Permissive-Mode zu schicken.
Die umfangreichen Manpages zu SELinux installiert man mit
yum -y install selinux-policy-devel
mandb
SELinux kommt auf RHEL nativ zum Einsatz. Auf Ubuntu lässt es sich nachinstallieren (AppArmor dabei deaktivieren).
Funktionsweise
Das Labeling-System:
Prozesse haben Label: Der Apache besitzt z.B. das Label httpd_t.
Ports haben Label: Der Apache darf im SELinux-Standard auf den Ports 80, 81, 443, 488, 8008, 8009, 8443 und 9000 lauschen.
Dateien und Verzeichnisse haben Label:
/var/www/html
besitzt das Label httpd_sys_content_t, oder httpd_sys_rw_content_t, wenn es beschreibbar sein darf. Dateien erhalten dafür erweiterte Attribute, die nur mit aktuellen Dateisystemen wie Ext3/Ext4/XFS gespeichert werden können.Der Kernel kontrolliert anhand einer Regel-Datenbank, ob diese Label zusammenspielen und was sie bewirken dürfen. Die SELinux-Datenbank, die diese Policies enthält, wird laufend durch die Installation von Software und Updates per Yum erweitert und gepflegt (in einer Minimal-Installation umfasst sie mindestens 6’000 Regeln, Tendenz steigend).
Ein Kontext-Label wie system_u:system_r:httpd_t:s0 besteht immer aus vier Komponenten:
User Component: system_u. Der SELinux-Benutzer. User Components enden meist auf _u. Andere Beispiele: guest_u, root.
Role Component: Rollenkonzept für Ressourcen. system_r beispielsweise für Prozesse, object_r für Dateien. Role Components enden meist auf _r. Andere Beispiele: staff_r.
Type Component: httpd_t. das wichtigste Feld, da SELinux im Standard ein Type Enforcement System ist. Der Typ endet meist auf _t.
Multi-Level Security (MLS) Component - oder kurz die „Sensitivity“: Identifiziert einen einzelnen Level oder einen Bereich. Steht in einer „targeted“-Policy immer auf s0 (Single Level).
SELinux würde zunächst jeden Zugriff eines Prozesses auf Systemressourcen jeglicher Art (Dateien, Verzeichnisse, Ports etc.) abweisen und nur selektiv anhand von Regeln Zugriffe erlauben - die so genannte „strict“-Policy. In der Praxis ist das nicht handhabbar, weswegen es mehrere Policies gibt:
Minimum
Multi-Level Security (MLS)-Enforcement: Erlaubt Prozessen Zugriff auf Daten anhand ihres Security Levels. Beispielsweise kann dann ein „Secret“-Prozess keine als „Top-Secret“ eingestuften Daten lesen.
Multi-Category Security (MCS)-Enforcement: Schützt Prozesse voreinander (wie virtuelle Maschinen, OpenShift Gears, SELinux Sandboxes, Container usw.).
Targeted (Single-Level Security - jeweils eine Policy pro Applikation): Die Standard-Policy in RHEL, die das Verhalten ausgewählter System-Prozesse kontrollierbar macht (zu finden unter
/etc/selinux/targeted
). In der „targeted“-Policy wird die Type-Komponente ausgewertet, nicht aber die Rollen- und Multi-Level-Komponente, weswegen man von einer „Type Enforcement Policy“ spricht. Alles andere läuft in der „unconfined“-Policy.Unconfined: Diese Programme werden nicht durch SELinux kontrolliert.
Mit sesearch
lässt sich ein Blick in die Policy-Datenbank werfen oder herausfinden, wie Policies und Booleans technisch definiert sind:
yum -y install setools-console
sesearch --allow
# RHEL 7
sesearch --bool rsync_export_all_ro --allow --show_cond
# RHEL 8+, Fedora
sesearch --bool rsync_export_all_ro --allow
Der Admin kann mit dem Befehl semanage
bequem Datei- und Port-Kontexte einsehen als auch eigene Regeln hinzufügen.
# up to RHEL 7
yum -y install policycoreutils-python
# RHEL 8+
dnf -y install policycoreutils-python-utils
semanage fcontext --list | grep http
semanage port --list | grep http
Welche Regeln werden auf das /var/www
-Verzeichnis angewendet?
semanage fcontext --list | grep /var/www | sort
Tipp
Diese man-Pages erklären SELinux: man -k selinux
Dateien und Verzeichnisse
Der Apache darf auf die Verzeichnisse und Dateien /var/www/html
und ~/public_html
zugreifen, da sie den SELinux-Dateikontext httpd_sys_content_t tragen, nicht aber auf die Passwort-Datei /etc/shadow
, da diese den Kontext shadow_t besitzt und keine Regel für Apache existiert, die den Zugriff darauf erlauben würde. Ein Eindringling, der den Apache kompromittiert hat, kann so nie an Systemdateien gelangen.
Gesetzte Datei-Labels ermittelt man immer mit dem Parameter -Z
:
ls -Z /etc/shadow
Dateien nehmen beim Verschieben ihren SELinux-Kontext mit, beim Kopieren erben sie den Kontext des Zielverzeichnisses. Verschobene Dateien müssen daher „umgelabelt“ werden, was man mit restorecon
erledigt.
Die „Datenbank“, in der alle SELinux Datei-Kontexte gepflegt sind, findet man hier:
cat /etc/selinux/targeted/contexts/files/file_contexts
...
/root(/.*)? system_u:object_r:admin_home_t:s0
...
/var/www(/.*)? system_u:object_r:httpd_sys_content_t:s0
...
Prozesse
Prozess-Labels ermittelt man immer mit dem Parameter -Z
:
id -Z
ps auxZ | grep httpd
- Prozess auf „permissive“ setzen
Beispiel logrotate: dieses läuft unter dem Kontext „logrotate_t“. Damit der Prozess auch bei Enforcing auf Dateien zugreifen kann (braucht man beispielsweise, wenn logrotate ein selbstgeschriebenes
postscript
ausführen soll, welches auf aussergewöhnliche Verzeichnisse zugreifen soll):semanage permissive --add logrotate_t
- Prozess wieder auf „enforcing“ setzen
semanage permissive --delete logrotate_t
SELinux-Booleans
Mit „Policy booleans“ lassen sich einfache SELinux-Sicherheitsvorgaben zur Laufzeit ein- bzw. ausschalten. Booleans sind eigentlich eine Art Shortcut für komplexere Policy-Module. Für fast jeden Prozess gibt es eine Reihe an Schaltern, beispielsweise erlaubt eine aktivierte Einstellung httpd_enable_cgi
dem Apache, CGI-Skripte auszuführen. Möchte der Administrator CGI-Skripte unterbinden, setzt er diesen Wert einfach auf 0/false/off. Die meisten Booleans sind standardmässig deaktiviert.
Eine Liste der installierten Booleans (ist immer installationsabhängig) erhält man mit getsebool -a
, gefiltert nach denen für Apache mit getsebool -a | grep http
. Ausführlicher mit Beschreibung und Default-Werten geht es mit Hilfe von semanage boolean --list --locallist
.
Um im obigen Beispiel CGI-Skripte zu deaktivieren, benutzt man setsebool
. Der Parameter -P
sorgt dafür, dass die Einstellung permanent gesetzt wird und somit einen Neustart überlebt.
setsebool -P httpd_enable_cgi off
Um den Schalter einfach auf seinen entgegengesetzten Wert umzulegen, kann man auch togglesebool httpd_enable_cgi
verwenden.
SELinux Audit-Log
Wird einer Software der Zugriff auf eine Ressource untersagt, landet dies schwer lesbar in der /var/log/audit/audit.log
, was im Fall eines Apache, der auf einen MariaDB-Server zugreifen wollte, so aussehen kann (und der Übersichtlichkeit halber optisch aufbereitet wurde):
type = AVC
msg = audit(1322708342.967:16804): avc: denied { name_connect } for pid = 2724
comm = "httpd"
dest = 3306
scontext = unconfined_u:system_r:httpd_t:s0
tcontext = system_u:object_r:mysqld_port_t:s0
tclass = tcp_socket
Das Format einer Nachricht im SELinux-Log kurz erklärt (Auszug):
type
:AVC
= „Access Vector Cache“ Message, Kernel Event,SYSCALL
= System-Aufruf.msg
: Enthält die Seriennummer der Nachricht. So erkennt man zusammenhängende Nachrichten.comm
: Name des Executables.dest
: Ziel, z.B. ein Port.scontext
: Security-Kontext des Source-Objektes.tcontext
: Security-Kontext des Target-Objektes.
Es gibt Tools, die einem beim Anschauen helfen oder sogar bis zu einer gewissen Grenze im Klartext erklären können, was seitens SELinux verboten wurde.
- ausearch
Statt
/var/log/audit/audit.log
direkt anzuschauen:ausearch --start recent --message avc --interpret
- audit2why
Dazu packt man die ermittelten
audit.log
-Zeilen in eine separate Datei und füttert diese anaudit2why
, einem Tool aus denpolicycoreutils-python
:# RHEL 7 yum -y install policycoreutils-python # RHEL 8+ dnf -y install policycoreutils-python-utils ausearch --start recent --message avc > /tmp/myaudit.log audit2why < /tmp/myaudit.log
- sealert
Wird das nachfolgende Paket installiert und der Server neu gestartet, tauchen lesbare SELinux-Meldungen in
/var/log/messages
bzw.journalctl
auf.yum -y install setroubleshoot-server sealert --analyze=/var/log/audit/audit.log #SELinux is preventing httpd from getattr access on the file /var/www/html/index.html.
Beispiel 1: Apache „Permission Denied“
Sobald SELinux im Enforcing-Mode läuft, wird der Webserver Probleme bekommen, Dateien aus /var/www/html
auszuliefern, die aus anderen Verzeichnissen dorthin verschoben wurden. Der Grund liegt im falschen Dateikontext; SELinux erwartet, dass Dateien, die durch httpd
aus /var/www
ausgeliefert werden dürfen, den Kontext httpd_sys_content_t
aufweisen.
Zur Übung erzeugt man eine index.html
im /tmp
-Ordner. Die Datei besitzt damit den Dateikontext unconfined_u:object_r:user_tmp_t:s0
. Verschiebt man sie in das /var/www/html
-Verzeichnis, bleibt der Kontext erhalten - SELinux hindert den Apache folgerichtig daran, die Datei bei einem Aufruf per HTTP auszuliefern, der Apache gibt brav eine „Forbidden“ (ich darf nicht)-Meldung aus.
Der Datei-Kontext für /var/www/.*
ist wohldefiniert und in der SELinux-Datenbank in Form von regulären Ausdrücken hinterlegt. So ermittelt man den resultierenden SELinux Security Context für Pfade und Dateien:
matchpathcon /var/www/html
Stimmt der Kontext, kann man die verschobenen Dateien einfach auf den Wert aus der Datenbank setzen:
restorecon -r /var/www/html
Damit File-Kontexte passend gesetzt werden können, muss man manchmal wissen, wie die Types/Transitions definiert sind. Hierbei helfen:
dnf -y install setools-console
# what does mysqld_t do?
sesearch --allow --source mysqld_t --class file --perm execute
# check the Transition rules
sesearch --type_trans --source mysqld_t
Ein Eintrag wie type_transition mysqld_t chkpwd_exec_t : process chkpwd_t;
bedeutet dabei: Falls MySQL etwas ausführt, das mit chkpwd_exec_t
gelabled ist, erhält der entstehende Prozess das Label chkpwd_t
.
Beispiel 2: Apache mit neuer DocumentRoot
Angenommen, die DocumentRoot eines virtuellen Apache-Hosts liegt unter /data
. SELinux hindert Apache an der Auslieferung der Dokumente, da dort erzeugte Dateien den sehr allgemeinen „unconfined_u:object_r:default_t:s0“-Kontext besitzen, der nicht zum Apache passt.
Hier muss die SELinux File-Context Datenbank ergänzt werden. /data
und alle enthaltenen Unterverzeichnisse sollen mit dem File-Kontext des Apache verknüpft werden:
semanage fcontext --add --type httpd_sys_content_t "/data(/.*)?"
Hinweis
restorecon
folgt keinen SymLinks, man muss bei der Definition von File-Contexts mit semanage also mit echten Pfadangaben arbeiten.
Falls der Apache Schreibrechte auf ein Verzeichnis benötigt, verwendet man folgenden Kontext:
semanage fcontext --add --type httpd_sys_rw_content_t "/data(/.*)?"
Anschliessend „restauriert“ man den Datei-Kontext unter /data
, sprich: man labelt sie anhand der aktualisierten SELinux-Datenbank um.
restorecon -Fvr /data
Die Manpage hilft bei der Syntax der Befehle:
man semanage-fcontext
Löschen eines Kontextes (Beispiel):
semanage fcontext --delete --type httpd_log_t "/data(/.*)?"
Beispiel 3: Apache darf kein Mail senden
Im folgenden Szenario soll ein PHP-Skript, welches ein Mail-Kommando absetzt, durch Apache ausgeführt werden. Das Skript ist durch root im /root
-Ordner erzeugt und nach /var/www/html
verschoben worden. Apache ist standardmässig über SELinux so konfiguriert, dass keine Mails versendet werden dürfen. Zusätzlich erwartet man hier Probleme wegen des falschen Datei-Kontextes.
Zunächst schafft man die Übungsvoraussetzungen, installiert Apache PHP, erzeugt das Skript und verschiebt es:
setenforce 1
yum -y install httpd php
systemctl restart httpd
firewall-cmd --add-service=http
setsebool -P httpd_can_sendmail off
cat >/root/test.php <<EOF
<?php
if (mail('root', 'subject', 'message')) {
echo 'OK';
}
else {
echo 'NOK';
}
?>
EOF
mv /root/test.php /var/www/html
chown apache:apache /var/www/html/test.php
Der Aufruf http://myserver/test.php wird durch den Apache mit einer leeren Seite und „Forbiden“ (403) quittiert. Wenn man mal so tut als wüsste man nicht warum, hilft folgende sequentielle Analyse:
tail /var/log/httpd/access_log
# "403" while accessing test.php
semanage permissive --add httpd_t
# Script works
semanage permissive --delete httpd_t
# Script won't work anymore
tail /var/log/httpd/error_log
# "Permission denied", so an OS related problem
Schnell noch die Datei-Berechtigungen prüfen: ein ls -l /var/www/html/test.php
zeigt, dass der Besitzer der Datei und die Gruppe zum Account des Apache passen, und User und Group Leserechte auf die Datei besitzen.
Man weiss längst: SELinux ist das „Problem“. Ein Aufruf des Skripts auf der Kommandozeile mit php /var/www/html/test.php
liefert ein „OK“, man muss den Fehler also im Apache-Kontext suchen. Ein Blick in die SELinux-Logs
tail /var/log/audit/audit.log
zeigt schon den falschen Target-Kontext tcontext=unconfined_u:object_r:admin_home_t:s0
. Erfahrungsgemäss schaut man sich mit ls -Z /var/www/html/test.php
den Datei-Kontext an und korrigiert:
restorecon /var/www/html/test.php
Ruft man die Seite erneut auf, erhält man jetzt immerhin ein „NOK“ - die Seite konnte aufgerufen, das Mail-Kommando jedoch nicht ausgeführt werden. SELinux hindert das im Apache-Kontext laufende PHP an der Mail-Kommunikation, worüber es im Audit-Log auch Auskunft gibt:
type = AVC
msg = audit(1410282955.098:649): avc:denied { search } for pid = 8840
comm = "sendmail"
name = "postfix"
dev = "dm-1"
ino = 9078592
scontext = system_u:system_r:httpd_t:s0
tcontext = system_u:object_r:postfix_etc_t:s0
tclass = dir
type = SYSCALL
msg = audit(1410282955.098:649): arch=c000003e syscall=2 success=no exit=-13 a0=7f38ae492ea0 a1=0 a2=0 a3=0 items=0 ppid=8511 pid=8840 auid=4294967295 uid=48 gid=48 euid=48 suid=48 fsuid=48 egid=48 sgid=48 fsgid=48 tty=(none) ses=4294967295
comm = "sendmail"
exe = "/usr/sbin/sendmail.postfix"
subj = system_u:system_r:httpd_t:s0 key=(null)
Die erste Zeile besagt, dass „sendmail“ über „postfix“ nicht ausgeführt werden durfte, Ursache („scontext“) war unser Apache-Prozess. Dafür gibt es einen SELinux-Boolean, wie man schnell herausfindet:
man httpd_selinux | grep mail
führt zu
setsebool -P httpd_can_sendmail on
Beispiel 4: Apache und entfernte MariaDB
Ein Python-Skript unter Apache möchte eine Verbindung zu einer entfernten MariaDB-Datenbank aufbauen, was verhindert wird. Die passende Zeile aus dem audit.log
wurde in einer eigenen /tmp/myaudit.log
abgelegt. Jetzt untersucht man, was zu tun ist:
ausearch --start recent --message avc > /tmp/myaudit.log
# type = AVC
# msg = audit(1322708342.967:16804): avc: denied { name_connect } for pid=2724
# comm = "httpd"
# dest = 3306
# scontext = unconfined_u:system_r:httpd_t:s0
# tcontext = system_u:object_r:mysqld_port_t:s0
# tclass = tcp_socket
yum -y install policycoreutils-python
dnf -y install policycoreutils-python-utils
audit2why < /tmp/myaudit.log
Die Ausgabe sagt, was man zu tun hat:
One of the following booleans was set incorrectly.
Description:
Allow HTTPD scripts and modules to connect to the network using TCP.
Allow access by executing:
# setsebool -P httpd_can_network_connect on
Description:
Allow HTTPD scripts and modules to connect to databases over the network.
Allow access by executing:
# setsebool -P httpd_can_network_connect_db on
Da das Skript sich zu einem „well-known“ Standard-Datenbank-Port verbinden möchte, genügt:
setsebool -P httpd_can_network_connect_db on
Beispiel 5: Apache auf einem anderen Port lauschen lassen
Da der Apache nur auf bestimmten Ports hören darf, wird die Konfiguration eines nicht standardkonformen Ports SELinux auf den Plan rufen. So fügt man eine neue Regel für Port 8080 hinzu:
semanage port --add --type http_port_t --proto tcp 8080
Die Manpage hilft bei der Syntax des Befehls:
man semanage-port | grep semanage
Beispiel 6: Apache „permissive“ laufen lassen
Nur, wenn es unbedingt sein muss, und man SELinux dann doch nicht komplett abschalten möchte:
semanage permissive --add httpd_t
Beispiel 7: Eigenes SELinux Module Package bauen
Siehe https://access.redhat.com/articles/5494701.
yum -y install setools-console
Dann:
- Tool oder Service permissive laufen lassen:
semanage permissive --add mysqld_t
systemd-run --unit fake_mysqld.service /usr/local/bin/mysqld
- AVCs sammeln:
ausearch --message AVC,USER_AVC --start recent
.Beispiel: Es wird ein AVC Denial für den Source Contextmysqld_t
und den Targetcifs_t
gemeldet.type=AVC msg=audit(...): avc: denied { getattr } for pid=XXX comm="mysqld" path="/cifs-server/data/file.csv" dev="cifs" ino=XXX scontext=system_u:system_r:mysqld_t:s0 tcontext=system_u:object_r:cifs_t:s0 tclass=file permissive=0
semanage permissive --delete mysqld_t
- Prüfen, ob es wirklich keine Rule für den gemeldeten Type gibt:RHEL 8+:
sesearch --allow --source mysqld_t --target cifs_t --class file --perm getattr
RHEL 7:sesearch --show_cond --allow --source mysqld_t --target cifs_t --class file --perm getattr
audit2allow --all
ausführen, um die Type Enforcement Regel anzuzeigen, die den verweigerten Zugriff erlaubt. Beispiel:
#============= httpd_t ==============
allow httpd_t var_log_t:file open;
Ist der Inhalt ok, kann das Custom-Modul generiert werden (würde auch mit einer Text-Datei funktionieren, die obiges enthält):
audit2allow --all --module-package=mymodulename
Modul aktivieren:
semodule --install mymodulename.pp
Beispiel 8: Eigene Module Packages verteilen
Siehe https://access.redhat.com/articles/5494701.
Wichtig: Es genügt, aus der .te-Datei die .pp-Datei einmalig zu compilieren und letztere auf weiteren Zielsystemen per semodule --install
zu installieren. Die .pp-Datei muss allerdings für jede Plattform separat compiliert werden (also je eine .pp-Datei für RHEL 7, RHEL 8 usw.). Ein Anwendungsbeispiel findet sich auf https://github.com/Linuxfabrik/monitoring-plugins/blob/main/BUILD.rst#compile.
Troubleshooting
- Fail to load SELinux policy. Freezing.
Wahrscheinlich ist das System falsch gelabelt. Kernel-Parameter
selinux=0
beim Booten mitgeben, um SELinux abzuschalten. Anschliessendtouch /.autorelabel && reboot
ausführen, um das System komplett umlabeln zu lassen. Danach ist Geduld gefordert, wenn das System viele Dateien hostet.
Built on 2024-09-30