SELinux - Heise Workshop Juni 2024

1 Agenda und Folien

1.1 SELinux Workshop Agenda

1.1.1 Tag 1

  • 09:00 Begrüssung
  • 09:20 SELinux und Linux Security Module
  • 11:00 Linux Audit Subsystem
  • 12:30 Mittagspause
  • 13:30 SELinux Label und Context
  • 15:00 SELinux entdecken
  • 17:00 Ende Tag 1

1.1.2 Tag 2

  • 09:00 SELinux Type Enforcement
  • 11:30 SELinux Fehler erkennen und beheben
  • 12:30 Mittagspause
  • 13:30 SELinux Richtlinien entwickeln
  • 16:30 SELinux - Offene Fragen / Q&A
  • 17:00 Ende

2 Allgemeine Informationen

  • Diese Anleitung und die Folien können online unter https://linux-sicherheit.org abgerufen werden
  • Bitte ersetzen Sie die Platzhalterzeichen NNN in der Anleitung durch Ihre Teilnehmernummer. Sie finden Ihre Teilnehmernummer in der Tabelle auf dieser Seite
  • Wenn nicht anders angegeben beziehen sich alle Konfigurations- und Kommando-Beispiele auf ein Red Hat Linux kompatibles System

3 Hostnamen, Benutzer und Passwörter

Anmeldung über SSH (OpenSSH, Putty, Google Chrome mit SSH-App) oder mit Browser an Port 443 (HTTPS) auf dem Server (z.B. https://selinux001.linux-sicherheit.org). Wählen Sie das Terminal auf der linken Seite des Cockpit-Webadministrationstools (wir benutzen in diesem Kurs keine der anderen Funktionen der Cockpit Web UI).

Leider meldet die Cockpit Web-Console bei Red Hat EL 9 kompatiblen Systemen derzeit, das ein aktueller Firefox Browser "zu alt" wäre. Dies ist ein Fehler, welcher noch nicht in diesem System gefixed wurde ("upstream" ist der Fehler schon behoben). Wenn dieser Fehler auftritt, dann bitte einen alternativen Browser oder den direkten SSH-Zugang benutzen.

  • Benutzername für die VMs: user
  • Kennwort: selinux2023
  • Root-Shell mit sudo -s und dem Benutzerpasswort
Benutzernummer Name SSH-Zugriff Web-Zugriff
001   selinux001.linux-sicherheit.org https://selinux001.linux-sicherheit.org
002   selinux002.linux-sicherheit.org https://selinux002.linux-sicherheit.org
003   selinux003.linux-sicherheit.org https://selinux003.linux-sicherheit.org
004   selinux004.linux-sicherheit.org https://selinux004.linux-sicherheit.org
005   selinux005.linux-sicherheit.org https://selinux005.linux-sicherheit.org
006   selinux006.linux-sicherheit.org https://selinux006.linux-sicherheit.org
007   selinux007.linux-sicherheit.org https://selinux007.linux-sicherheit.org
008   selinux008.linux-sicherheit.org https://selinux008.linux-sicherheit.org
009   selinux009.linux-sicherheit.org https://selinux009.linux-sicherheit.org
010   selinux010.linux-sicherheit.org https://selinux010.linux-sicherheit.org
011   selinux011.linux-sicherheit.org https://selinux011.linux-sicherheit.org
012   selinux012.linux-sicherheit.org https://selinux012.linux-sicherheit.org
013   selinux013.linux-sicherheit.org https://selinux013.linux-sicherheit.org

4 Linux Security Modules und SELinux

5 Audit Subsystem

5.1 Audit Log-Daemon Installation

  • Wir arbeiten auf den virtuellen Maschinen im Internet
  • Audit-Subsystem Programme installieren. Das Paket audit ist in der Regel schon vorinstalliert. Das Paket audispd-plugins enthält die Plugins des Audit-Dispatchers
dnf install audit audispd-plugins

5.2 Konfiguration

  • Passe die Audit-Daemon Konfiguration an. Benutze hierfür die Informationen aus den Folien
$EDITOR /etc/audit/auditd.conf
  • Audit-Dämon anschalten (so dass er bei einem Restart neu gestartet wird) und prüfen das der Prozess läuft
systemctl enable --now auditd
systemctl status auditd
journalctl -u auditd
  • Dem Audit-Daemon ein "Hangup (HUP)" Signal senden um die Konfiguration neu zu laden
pkill -HUP auditd
  • Audit-Daemon starten und stoppen
service auditd stop
service auditd start

5.3 Ad-Hoc Trace von Programmen

  • Starte einen Audit-Subsystem Trace eines Prozesses (hier 4 x ICMP Echo per ping)
autrace /bin/ping -c 4 8.8.8.8
  • Audit Log für einen speziellen autrace Aufruf anschauen. Die genaue Kommandozeile wurde vom autrace Programm ausgegeben.
ausearch -i -p <audit-id>
  • Die Log-Ausgaben können über eine Shell-Pipeline ausgewertet werden. In diesem Beispiel werden alle System-Calls des autrace Aufruf aufgelistet und nach Häufigkeit sortiert ausgeben:
ausearch -i -p <audit-id> | grep type=SYSCALL | cut -f 6 -d ' ' | sort | uniq -c | sort -n
  • Wurde der Hostname als Metadaten bei den Audit-Meldungen mit ausgegeben Konfiguration des Audit-Daemon), so muss per cut Befehl das Feld #7 ausgewertet werden (...| cut -f 7 -d ' ' |...)

5.4 Richtlinien-Dateien

  • Die vorinstallierte (leere) Richtlinien-Datei löschen
    rm /etc/audit/rules.d/audit.rules
    
  • Audit-Richtlinien Beispiele befinden sich unter /usr/share/audit/sample-rules
  • Richtliniendateien aus den Beispielen in das Verzeichnis für die Audit-Regeln kopieren
cp /usr/share/audit/sample-rules/10-base-config.rules /etc/audit/rules.d/
cp /usr/share/audit/sample-rules/11-loginuid.rules /etc/audit/rules.d/
cp /usr/share/audit/sample-rules/30-stig.rules /etc/audit/rules.d/
cp /usr/share/audit/sample-rules/32-power-abuse.rules /etc/audit/rules.d/
cp /usr/share/audit/sample-rules/42-injection.rules /etc/audit/rules.d/
cp /usr/share/audit/sample-rules/43-module-load.rules /etc/audit/rules.d/
cp /usr/share/audit/sample-rules/70-einval.rules /etc/audit/rules.d/
cp /usr/share/audit/sample-rules/71-networking.rules /etc/audit/rules.d/
cp /usr/share/audit/sample-rules/99-finalize.rules /etc/audit/rules.d/
  • Richtlinien für priviligierte Programme erstellen
cd /etc/audit/rules.d/
find /bin -type f -perm -04000 2>/dev/null | awk '{ printf "-a always,exit -F path=%s -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged\n", $1 }' > 31-privileged.rules
find /sbin -type f -perm -04000 2>/dev/null | awk '{ printf "-a always,exit -F path=%s -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged\n", $1 }' >> 31-privileged.rules
find /usr/bin -type f -perm -04000 2>/dev/null | awk '{ printf "-a always,exit -F path=%s -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged\n", $1 }' >> 31-privileged.rules
find /usr/sbin -type f -perm -04000 2>/dev/null | awk '{ printf "-a always,exit -F path=%s -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged\n", $1 }' >> 31-privileged.rules
filecap /bin 2>/dev/null | sed '1d' | awk '{ printf "-a always,exit -F path=%s -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged\n", $2 }' >> 31-privileged.rules
filecap /sbin 2>/dev/null | sed '1d' | awk '{ printf "-a always,exit -F path=%s -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged\n", $2 }' >> 31-privileged.rules
filecap /usr/bin 2>/dev/null | sed '1d' | awk '{ printf "-a always,exit -F path=%s -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged\n", $2 }' >> 31-privileged.rules
filecap /usr/sbin 2>/dev/null | sed '1d' | awk '{ printf "-a always,exit -F path=%s -F perm=x -F auid>=1000 -F auid!=unset -F key=privileged\n", $2 }' >> 31-privileged.rules
  • Die ursprüngliche Audit-Richtlinien-Datei anschauen
    less /etc/audit/audit.rules
    
  • Die Richtlinie kompilieren und prüfen
    augenrules --check
    
  • Die neue Policydatei in den Kernel laden
    augenrules --load
    
  • Die kombinierte Audit-Richtlinien-Datei anschauen
    less /etc/audit/audit.rules
    
  • Aktive Audit-Regeln auflisten
    auditctl -l
    
  • Status des Audit-Subsystems anzeigen
    # auditctl -s
    enabled 2
    failure 1
    pid 12901
    rate_limit 0
    backlog_limit 8192
    lost 0
    backlog 0
    backlog_wait_time 0
    loginuid_immutable 0 unlocked
    

5.5 Beispiele für Audit-Abfragen

  • Alle Audit-Einträge zum Thema "sudo" zeigen
    ausearch -i -x sudo
    
  • Report über fehlgeschlagende Anmeldeversuche
    ausearch -m USER_AUTH,USER_ACCT --success no
    
  • Alle Audit-Meldungen für Benutzer UID 1000
    ausearch -ua 1000 -i
    
  • Fehlgeschlagende Syscalls seit gestern
    ausearch --start yesterday --end now -m SYSCALL -sv no -i
    

5.6 Aufgabe:

  • Das Audit-Subsystem um neue Regel(n) zu Systemd erweitern:
    • Alle Systemd-Konfigurationsdateien mit der Endung *.conf, welche direkt unter /etc/systemd (nicht in den Unterverzeichnissen) gespeichert sind, sollen auf Schreibzugriffe überwacht werden
    • Die Audit-Events sollen mit dem Key systemd markiert werden
    • Die neue Richtlinie kompilieren und aktivieren
    • Die neue Richtlinie testen, in dem manuell an einer der überwachten Dateien eine Änderung vorgenommen wird
    • Die Audit-Meldungen per ausearch anzeigen und analysieren (suchen nach Events mit dem Key systemd)

5.7 Beispiel-Lösung

  • Erweiterung der Audit-Regeln
[...]
# Systemd Konfiguration
-w /etc/systemd/journald.conf -p wa -k systemd
-w /etc/systemd/logind.conf -p wa -k systemd
-w /etc/systemd/networkd.conf -p wa -k systemd
-w /etc/systemd/resolved.conf -p wa -k systemd
-w /etc/systemd/sleep.conf -p wa -k systemd
-w /etc/systemd/system.conf -p wa -k systemd
-w /etc/systemd/timesyncd.conf -p wa -k systemd
-w /etc/systemd/user.conf -p wa -k systemd
[...]
  • Suche nach Audit-Events mit dem Schlüssel systemd
ausearch -i -k systemd

6 SELinux

6.1 SELinux erkunden

  • SELinux Hilfspakete installieren
dnf install policycoreutils setools libselinux-utils selinux-policy-doc setools-console
dnf install policycoreutils-python3 selinux-policy-devel policycoreutils-newrole

6.1.1 SELinux Label auf Dateien

  • SELinux Label (Context) auf Dateien anzeigen
    ls -lZ <pfad>
    
  • Welche Dateien/Verzeichnisse unter /etc sind vom SELinux Typ system_conf_t?
  • Welchen SELinux Type haben neue Dateien im Heimverzeichnis des Benutzers user (ggf. eine Datei neu anlegen)?
  • Erstelle eine sortierte Liste der SELinux Datei-Typen (3tes Feld im SELinux Label user:role:type) im Verzeichnis /usr/sbin.
  1. Lösungen
    • Frage 1:
    # ls -lZ /etc | grep system_conf_t
    -rw-r--r--.  1 root root   system_u:object_r:system_conf_t:s0           449 Jan 23 20:06 sysctl.conf
    drwxr-xr-x.  2 root root   system_u:object_r:system_conf_t:s0           124 Dec 13 06:15 yum.repos.d
    
    • Frage 2:
    [root@selinux016 user]# touch /home/user/test
    [root@selinux016 user]# ls -lZ /home/user/test
    -rw-r--r--. 1 root root unconfined_u:object_r:user_home_t:s0 0 Jan 31 07:39 /home/user/test
    
    • Frage 3:
    ls -lZ /usr/sbin/ | cut -f 3 -d ':' | sort | uniq -c | sort -n
    

6.1.2 SELinux Label auf Prozessen

  • SELinux Label auf Prozessen anzeigen
    ps -auxZ
    
  1. Aufgabe: Label auf Prozessen
    • Wieviel verschiendene SELinux Benutzer, Rollen und Typen gibt es bei den laufenden Prozessen im RedHat 9 System?
  2. Lösung:
    • Benutzer: 3
    • Rollen: 3
    • Typen: 26
    ps -efZ | cut -d ':' -f 3 | sort | uniq | wc -l
    

6.1.3 SELinux Label auf dem aktuellen Benutzer anzeigen

# id -Z

6.1.4 SELinux Status abfragen

  • Allgemeinen SELinux Status abfragen
sestatus
  • Detaillierten SELinux Status abfragen
sestatus -v
  • SELinux "enforcement" Status anzeigen
getenforce
  • Verfügbare SELinux Module auflisten
semodule -l | less
  • Die kompilierte (binäre) Richtlinien (Policy) Datei
ls -lh /etc/selinux/targeted/policy/
  • Statistiken über den Access-Vector-Cache (AVC)
# avcstat
   lookups       hits        misses     allocs   reclaims      frees
   797921406     797847473   73933      73933    71040         73429

6.2 SELinux Policy Konfiguration anpassen

6.3 SELinux Funktions-Beispiel

  • SELinux in den enforcing Modus schalten
setenforce 1
  • Apache Webserver installieren und starten
dnf install httpd
systemctl enable --now httpd
  • Kleine Webseite mit einem Editor anlegen ($EDITOR durch vi, vim, nano, emacs etc ersetzen)
$EDITOR /var/www/html/index.html
  • Inhalt der HTML-Datei /var/www/html/index.html (Vorschlag)
<html>
<body>
<h1>Apache Webserver</h1>
</body>
</html>
  • SELinux Security Context auf der Datei anzeigen
ls -lZ /var/www/html/index.html
  • Port 80 in der Firewall erlauben
# firewall-cmd --zone=public --add-service=http --permanent
# firewall-cmd --reload
  • Die Webseite sollte nun unter Port 80 (http, nicht https) mittels eines Webbrowsers abrufbar sein

6.3.1 Ein SELinux-Problem für den Apache Webserver erzeugen

  • Eine Datei index.html Datei im Heim-Verzeichnis des Benutzers root erstellen und in das Apache-WWW-Verzeichnis verschieben (nicht kopieren):
    rm /var/www/html/index.html
    $EDITOR /root/index.html
    mv /root/index.html /var/www/html/index.html
    ls -lZ /var/www/html/index.html
    
  • Apache sollte nun nicht mehr in der Lage sein, die HTML-Datei auszuliefern (Default-Apache 2 Webseite erscheint)
  • SELinux LSM-Meldungen im Audit-Log zum Prozess httpd anzeigen (Modul avc = Access Vector Cache)
    ausearch -m avc -ts recent -c httpd -i
    
    • SELinux Sicherheits-Kontext der Datei prüfen
    matchpathcon -V /var/www/html/index.html
    
    • Es wird angezeigt das der SELinux Sicherheits-Kontext der Datei nicht korrekt ist. SELinux blockiert daher die Zugriffe vom Apache Webserver auf diese Datei

6.3.2 Das SELinux Problem mit dem Apache Webserver lösen

  • SELinux Security Context für Apache-Dateien anzeigen
    sesearch --allow --source httpd_t --target httpd_sys_content_t --class file
    
  • SELinux Sicherheits-Kontext anpassen (manuell mit dem Befehl chcon (Change Context)
    chcon --type httpd_sys_content_t /var/www/html/index.html
    
  • (Alternativ) SELinux Sicherheits-Kontext aus der SELinux Policy angleichen
    restorecon -v /var/www/html/index.html
    

6.4 Aufgabe: BIND 9 Zonendatei in ungewöhnlichem Verzeichnis

  • In dieser Aufgabe wollen wir die BIND 9 Zonendateien aus operativen Gründen unterhalb des /srv/ Filesystem-Pfad speichern
  • In dieser Aufgabe arbeiten wir mit dem BIND 9 DNS Server. Installation des BIND 9 Servers
    # dnf install bind
    
  • Starte den BIND 9 DNS Server
    # systemctl enable --now named
    
  • Erstellen Sie das Verzeichnis /srv/bind/zones/primary/
    % mkdir -p /srv/bind/zones/primary
    
  • Erstellen Sie eine Zonendatei mit einem Editor (z.B. vi) für die Zone example.net in diesem neuen Verzeichnis
    $ttl 3600
    @      IN SOA selinuxNNN.linux-sicherheit.org.  .  1001  2h 1h 40d 1h
           IN NS  selinuxNNN.linux-sicherheit.org.
           IN A   1.2.3.4
    
  • Erstellen Sie einen neuen Zonen-Block ür die neue primäre Zone in der BIND 9 Konfigurationsdatei /etc/named.conf
    zone "example.net" {
      type master;
      file "/srv/bind/zones/primary/example.net";
    };
    
  • Prüfe die Konfiguration (named-checkconf), lade die Konfiguration in den BIND 9 DNS Server (rndc reload) und prüfe den Zonen-Status der Zone example.net
    % named-checkconf -z
    % rndc reload
    % rndc zonestatus example.net
    
  • Die Zone wurde nicht geladen
  • Suchen Sie mittels journalctl und ausearch nach den Gründen
    % journalctl -eu named
    % ausearch -m avc -x /usr/sbin/named -i
    
  • Wie kann dieses SELinux Problem gelöst werden?

6.4.1 Lösung

  • SELinux Security Context Type-Enforcement Definitionen für BIND 9-Dateien anzeigen
    sesearch --allow --source named_t --class file
    
  • Aus der Liste schaut named_zone_t vielversprechend aus
  • Ändern Sie die SELinux Policy für den BIND 9 Server und übernehmen Sie das neue Verzeichnis als ein Verzeichnis für BIND 9 Zonendateien
    % semanage fcontext -a -t named_zone_t --ftype f "/srv/bind/zones(/.*)?"
    % semanage fcontext -a -t named_zone_t --ftype d "/srv/bind/zones(/.*)?"
    
  • Wenden Sie die neuen SELinux Datei-Label auf die existierenden Zonendateien an
    % restorecon -rv /srv/bind/zones/
    Relabeled /srv/bind/zones from unconfined_u:object_r:var_t:s0 to unconfined_u:object_r:named_zone_t:s0
    Relabeled /srv/bind/zones/primary from unconfined_u:object_r:var_t:s0 to unconfined_u:object_r:named_zone_t:s0
    Relabeled /srv/bind/zones/primary/example.net from unconfined_u:object_r:var_t:s0 to unconfined_u:object_r:named_zone_t:s0
    
  • Danach den BIND 9 DNS Server neu starten (oder neu laden)
    systemctl restart named
    
  • Die Zone sollte nun korrekt geladen sein
    rndc zonestatus example.net
    

6.5 SELinux Richtlinien über Schalter konfigurieren am Beispiel BIND 9

  • In dieser Aufgabe arbeiten wir mit dem BIND 9 DNS Server. Installation des BIND 9 Servers
    # dnf install bind
    
  • Starte den BIND 9 DNS Server
    # systemctl enable --now named
    
  • Der BIND 9 DNS kann interne Statistiken über das HTTP Protokoll ausliefern. Der Standardport für diese Funktion ist 8053.
  • In der Datei /etc/named.conf füge die folgende Definition des Statistik-Channels hinzu (am Beginn der Konfigurationsdatei)
    statistics-channels {
          inet * port 8053 allow { any; };
    };
    
  • Für Produktions-Installationen sollte statt any der Zugriff auf diesen Dienst per IP-Adressen ACL beschränkt werden
  • Die Konfiguration prüfen und den BIND 9 DNS Server neu starten
    # named-checkconf
    # systemctl restart named
    
  • Im Journal-Log des BIND 9 DNS Servers sollte eine Meldung auftauchen, das der Port 8053 nicht geöffnet werden konnte
    # journalctl -u named
    [...]
    Oct 17 18:08:45 selinux22 named[34204]: /etc/named.conf:11: couldn't allocate statistics channel 0.0.0.0#8053: permission denied
    
  • Der Fehler im Audit-Log
 # ausearch -m avc -ts recent  -i
----
type=PROCTITLE msg=audit(09/17/2021 04:06:05.836:2278) : proctitle=/usr/sbin/named -u named -c /etc/nam
type=SYSCALL msg=audit(09/17/2021 04:06:05.836:2278) : arch=x86_64 syscall=bind success=no
    exit=EACCES(Permission denied) a0=0x15 a1=0x7f5183d24660 a2=0x10 a3=0x7f5183d244fc
    items=0 ppid=111613 pid=111615 auid=unset uid=named gid=named euid=named suid=named
    fsuid=named egid=named sgid=named fsgid=named tty=(none) ses=unset
    comm=isc-worker0000 exe=/usr/sbin/named subj=system_u:system_r:named_t:s0 key=(null)
type=AVC msg=audit(09/17/2021 04:06:05.836:2278) : avc:  denied  { name_bind } for  pid=111615
    comm=isc-worker0000 src=8053 scontext=system_u:system_r:named_t:s0
    tcontext=system_u:object_r:unreserved_port_t:s0
    tclass=tcp_socket permissive=0

6.5.1 Setzen des Boolean, um BIND 9 den Zugriff auf die http-Ports zu erlauben

  • Suche mit sesearch nach Berechtigungen von Prozessen mit Label named_t:
    # sesearch --allow --source named_t --class tcp_socket
    
  • Das Umschalten des SELinux-Boleans named_tcp_bind_http_port erlaubt den Zugriff auf Ports, die für den Typ http_port_t definiert sind
    # setsebool named_tcp_bind_http_port=on
    
  • Aber Port 8053 ist nicht unter diesen Ports 😕
    # semanage port -l | grep http_port_t
    http_port_t tcp     80, 81, 443, 488, 8008, 8009, 8443, 9000
    
  • Lösung 1: Benutze einen Port für den Statistik-Channel, welcher in der SELinux Policy für http_port_t erlaubt ist. Beispiel: In der named.conf port 8008:
    statistics-channels {
      inet * port 8008 allow { any; };
    };
    
  • Lösung 2: Füge den BIND 9 Statistik-Port 8053 der Liste der Port für http_port_t hinzu:
    # semanage port -a -t http_port_t -p tcp 8053
    # semanage port -l | grep http_port_t
    http_port_t                    tcp  8053, 80, 81, 443, 488, 8008, 8009, 8443, 9000
    
  • Danach den BIND 9 DNS Server neu laden
    systemctl restart named
    
  • Die BIND 9 Statistik-Webseite sollte nun unter dem Namen der virtuellen Maschine selinuxNNN.linux-sicherheit.org und der Angabe des korrekten Ports in der URL (8008 oder 8053, je nach Lösung) mit einem Web-Browser abrufbar sein (ggf. muss noch in der Firewall der Port freigeschaltet werden – siehe Beispiel oben)

6.6 Aufgabe: Eine dynamische DNS Zone

  • Erstelle auf der VM eine neue DNS Zonendatei für den DNS Server unter /var/named/example.org. Der Inhalt dieser Datei (NNN durch die eigene Teilnehmer-Nummer ersetzen):
$ttl 3600
@      IN SOA selinuxNNN.linux-sicherheit.org.  .  1001  2h 1h 40d 1h
       IN NS  selinuxNNN.linux-sicherheit.org.
       IN A   1.2.3.4
  • Prüfe den SELinux Sicherheit Kontext auf der neuen Zonendatei:
% ls -Z /var/named/example.org
unconfined_u:object_r:named_zone_t:s0 /var/named/example.org
  • Erstelle eine neue Zonen-Definition in der BIND 9 Konfigurations-Datei /etc/named.conf
zone "example.org" {
     type master;
     file "example.org";
};
  • Prüfe die neue Konfiguration und lade die Konfiguration in den BIND 9 DNS Server
% named-checkconf -z
% rndc reconfig
% rndc zonestatus example.org
  • Editiere die BIND 9 Konfigurationsdatei /etc/named.conf und verwandle die Zone example.com in eine dynamische Zone (eine Zone welche nicht mehr per Editor, sondern über das DNS Protokoll aus dem Netzwerk verändert wird):
zone "example.org" {
     type master;
     file "example.org";
     update-policy local;
     };
  • Prüfe die Konfiguration mit named-checkconf -z, lade die Konfiguration und prüfe den Status der Zone mittels rndc zonestatus. Die Zone sollte nun als eine dynamsiche Zone angezeigt werden.
  • Erzeuge einen neuen IPv6-Adressen-Eintrag in der Zone (mittels nsupdate Programm):
% nsupdate -l
> ttl 3600
> add example.org in aaaa 2001:db8::1
> send
> quit
  • Die Änderungen der Datei werden erst mit einer Verzögerung (max 15. Minuten) vom BIND 9 DNS Server in das Dateisystem geschrieben. Mit dem Befehl rndc sync kann das Schreiben der Änderungen erzwungen werden:
# rndc sync
  • Das dynamische Update schlägt fehl. Warum?
  • Prüfe das BIND 9 Log:
% journalctl -eu named
  • Prüfe das Audit-Log:
% ausearch -m avc -x /usr/sbin/named -i
  • Wie können wir dieses Problem lösen?

6.6.1 Lösung 1

  • Verschiebe die Zonen-Datei in das Verzeichnis /var/named/dynamic (in diesem Verzeichnis darf der BIND 9 Nameserver Dateien schreiben) und passe den SELinux Datei-Kontext (und die Unix-Berechntigungen) an:
% mv example.org dynamic/
% chown named: dynamic/example.org
% restorecon -vr dynamic/
Relabeled /var/named/dynamic/example.org from unconfined_u:object_r:named_zone_t:s0 to unconfined_u:object_r:named_cache_t:s0
  • Passe die BIND 9 Konfiguration an:
zone "example.org" {
     type master;
     file "dynamic/example.org";
     update-policy local;
     };

6.6.2 Lösung 2

  • Setze den SELinux Boolean Schalter named_write_master_zones auf on
% setsebool named_write_master_zones=on

6.7 Aufgabe: Apache auf einem anderen Port als 80 oder 443

  • In dieser Aufgabe soll ein Apache 2 Webserver installiert werden. Aus operativen Gründen muss dieser Webserver auf Port 1235 auf Anfragen von Browser-Clients horchen
  • Den Apache 2 Webserver installieren und starten
    # dnf install httpd
    # systemctl enable --now httpd
    # systemctl status httpd
    
  • Ändere die Apache Konfiguration unter /etc/httpd/conf/httpd.conf so das der Apache auf Port 1235/TCP auf HTTP-Anfragen horcht (Konfigurationsparameter Listen: 1235)
  • Versuche den Apache Webserver mittels systemd neu zu starten (systemctl restart httpd). Dies wird fehlschlagen, da httpd sich nicht auf Port 1235 binden kann.
  • Schaue in per ausearch in die Audit-Logdatei nach SELinux Fehlern des Prozesses httpd
    ausearch -i -m avc -c httpd
    
  • Erlaubte Ports für den Apache HTTP auflisten
    semanage port -l | grep http_port_t
    
  • Benutze den Befehl semanage port um den Port 1235 für den Prozess httpd im SELinux freizuschalten und starte den Apache Webserver neu.
  • Port 1235 in der Firewall freischalten
    # firewall-cmd --zone=public --add-port=1235/tcp --permanent
    # firewall-cmd --reload
    
  • Teste, ob der Firefox-Browser unter der URL http://selinuxNNN.linux-sicherheit.org:1235/ die Webseite des Webservers sehen kann.
  • (Alternativ): Teste, ob die Webseite mit curl von der VM aus abrufbar ist:
    curl  http://selinuxNNN.linux-sicherheit.org:1235/
    
  1. Beispiellösung
    • Apache Konfiguration bearbeiten
    $EDITOR /etc/httpd/conf/httpd.conf
    
    • Apache Prozess neu starten. Dies sollte fehlschlagen, da SELinux die Benutzung von Port 1235 für den Prozess httpd verbietet
    • SELinux Fehler für httpd im Audit-Log suchen
    ausearch -m avc -c httpd -ts recent -i
    
    • Port 1235 für Apache in der SELinux Richtlinie hinzufügen
    semanage port -a -t http_port_t -p tcp 1235
    
    • Neue Portdefinition prüfen
    semanage port -l | grep http_port_t
    
    • Apache Webserver neu starten
    systemctl restart httpd
    
    • Port 1235 in der Firewall freischalten
    firewall-cmd --zone=public  --add-port=1235/tcp --permanent
    firewall-cmd --reload
    
    • Änderungen mit semanage sind persistent, die Port-Änderung ist unter /var/lib/selinux/targeted/active/ports.local abgespeichert:
    # This file is auto-generated by libsemanage
    # Do not edit directly.
    
    portcon tcp 1235 system_u:object_r:http_port_t:s0
    

6.8 SELinux und Benutzer

6.8.1 SELinux Benutzer zuweisen

  • Derzeitige Zuweisungen von SELinux Benutzern anzeigen
    # semanage login -l
    
  • Verfügbare SELinux Benutzer und Rollen anzeigen
    # semanage user -l
    
  • Benutzer user in die Benutzerklasse user_u einfügen.
    semanage login -a -s user_u user
    
  • Neu als Benutzer user anmelden (z.B. über SSH oder Cockpit) und ein Terminal öffnen
  • Die Befehle su oder sudo zur Ausweitung von Rechten sollten nun für den Benutzer user nicht mehr möglich sein
  • Die Benutzerzuweisungen wurden in /etc/selinux/targeted/seusers gesichert:
    # cat /etc/selinux/targeted/seusers
    
  • Ein Shellscript hello.sh im Heimverzeichnis des Benutzers user erstellen und ausführbar machen
    $EDITOR /home/user/hello.sh
    chmod +x /home/user/hello.sh
    
  • Inhalt des Shell-Scripts
    #!/bin/sh
    echo "Hallo!"
    
  • Testen, das dieses Shell-Skript direkt durch Aufruf der Datei ausgeführt werden kann
    # /home/cas/hello.sh
    Hallo!
    
  • In einer neuen Sitzung als root den Benutzer user in die SELinux-Benutzer Gruppe guest_u aufnehmen (Gäste haben im SELinux System weniger Rechte als reguläre Benutzer) und die direkte Ausführung von Skripten unterbinden (über einen SELinux Schalter (Boolean) ausschalten)
    semanage login -m -s guest_u user
    getsebool allow_guest_exec_content
    setsebool allow_guest_exec_content off
    
  • Versuchen als Benutzer user das Shell-Skritp auszuführen. Shell-Skripte sind nun nicht mehr direkt ausführbar
  • Die SELinux Meldungen (avc) im Audit Log anschauen
    ausearch -m avc -ts recent
    
  • Welche Möglichkeit(en) gibt es für Benutzer user, trotz der SELinux Beschränkung Shell-Skripte auszuführen?
  1. Lösung:
    • Shell Skripte können durch Aufruf des Skript-Interpreters (z.B. /bin/sh oder /bin/bash) mit der Skript-Datei ausgeführt werden
      # /bin/bash /home/cas/hello.sh
      Hello!
      
    • Shell-Skripte können u.U. auch direkt in der laufenden Shell ausgeführt werden:
      # . ./hello.sh
      # source ./hello.sh
      

6.9 SELinux - Policy Development

  • Für dieses Kapitel arbeiten wir auf dem VM Maschinen
  • Andere Web-Server (Apache/NGINX etc) auf der VM stoppen
  • Für die Dauer dieser Übung die Firewall auf der VM deaktivieren
systemctl stop firewalld
systemctl stop httpd

6.9.1 SELinux Policy erstellen

  • In diesem Kapitel werden wir eine SELinux Richtlinie (Policy) für einen Dienst entwickeln, welcher bisher noch nicht durch SELinux geschützt ist
    • Dies kann in der Praxis für Software notwendig werden, welche von externen Entwicklern geliefert wurde oder im eigenen Hause entwickelt wird
    • Unsere Beispiel-Anwendung ist ein sehr einfacher Webserver
  • Um die Beispiel-Anwendung aus dem Quellcode übersetzen zu können installieren wir den GCC C-Compiler
dnf install gcc
  • Für die Entwicklung neuer SELinux Richtlinien benötigen wir die Pakete für die SELinux Policy-Entwicklung (diese sind im Kurs ggf. schon installiert)
dnf install policycoreutils-python3 selinux-policy-devel
  • Nach der Installation dieser Pakete befinden sie die SELinux Policy Quelldateien (der von Red Hat und der Community erstellen Richtlinien) im Verzeichnis /usr/share/selinux/devel/
    ls -l /usr/share/selinux/devel/
    ls -l /usr/share/selinux/devel/include/contrib/
    

6.9.2 Ein einfacher Web-Server

  • Hier ist der Quellcode (C Programmiersprache) eines (sehr) einfachen Web-Servers. Dieser Webserver liefert nur eine statische Webseite aus (diese Webseite ist fest im Quellcode des Servers eingebaut und wird nicht aus dem Dateisystem geladen):
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <err.h>

char response[] = "HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=UTF-8\r\n\r\n"
"<!DOCTYPE html><html><head><title>Bye-bye baby bye-bye</title>"
"<style>body { background-color: #111 }"
"h1 { font-size:4cm; text-align: center; color: black;"
" text-shadow: 0 0 2mm red}</style></head>"
  "<body><h1>Goodbye, world!</h1></body></html>\r\n";

int main()
{
  int one = 1, client_fd;
  struct sockaddr_in svr_addr, cli_addr;
  socklen_t sin_len = sizeof(cli_addr);

  int sock = socket(AF_INET, SOCK_STREAM, 0);
  if (sock < 0)
    err(1, "can't open socket");

  setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int));
  int port = 8080;
  svr_addr.sin_family = AF_INET;
  svr_addr.sin_addr.s_addr = INADDR_ANY;
  svr_addr.sin_port = htons(port);

  if (bind(sock, (struct sockaddr *) &svr_addr, sizeof(svr_addr)) == -1) {
    close(sock);
    err(1, "Can't bind");
  }

  listen(sock, 5);
  while (1) {
    client_fd = accept(sock, (struct sockaddr *) &cli_addr, &sin_len);
    printf("got connection\n");

    if (client_fd == -1) {
      perror("Can't accept");
      continue;
    }

    write(client_fd, response, sizeof(response) - 1); /*-1:'\0'*/
    close(client_fd);
  }
}

  • Wir erstellen ein neues Verzeichnis für unser Project und erstellen die Datei mit dem Quellcode mit Hilfe eines Text-Editors (emacs, mg, nano, vim etc)
mkdir ~/src
cd ~/src
$EDITOR simple-server1.c
  • Im nächsten Schritt wird der Quellcode in eine Programm-Datei übersetzt (simple-server)
gcc -o simple-server simple-server1.c
  • Die Programmdatei kopieren wir in das Verzeichnis /usr/local/bin
cp simple-server /usr/local/bin
  • Wir starten den Server im Hintergrund und testen die Funktion in dem wir uns mit einem Web-Browser an Port 8080 verbinden. Wir sollten dort eine "HelloHHHHHGoodbye World" Meldung sehen. Bei jeder Verbindung gibt der Server den Text Got connection aus.
simple-server &
  • Wenn wir uns die SELinux Label des Dienstes anzeigen lassen sehen wir das dieses Dienst unconfined ist, also nicht durch SELinux abgesichert
ps -eZ | grep simple
ls -lZ /usr/local/bin/simple-server

6.9.3 Initiales SELinux Policy Modul

  • Wir erstellen ein neues Verzeichnis fuer das neue SELinux Policy Modul
mkdir ~/selinux-src
cd ~/selinux-src
  • Der Befehl sepolicy generate erzeugt eine Vorlage für ein SELinux Policy Modul
sepolicy generate -n simple-server --init /usr/local/bin/simple-server
  • Es werden drei SELinux Policy Quelldateien erstell
    • simple-server.te - Type Enforcement Quellcode - auf welche Datei-Typen darf der Prozess zugreifen
    • simple-server.fc - File Context Quellcode - welche Datei-Typen werden benutzt (und in welchen Pfaden liegen diese)
    • simple-server.if - Interface Quellcode - Definiert die Übergänge zwischen den Dateisystem und Prozess Typen, und definiert die Regeln für die Richtlinien
    • simple-server_selinux.spec - Quelldatei für ein RPM Paket
    • simple-server.sh - Shell Skript zum bauen des RPM Pakets des SELinux Policy Moduls
  • Diese Dateien können wir uns anschauen
  • Die Policy ist im permissive Modus!
less simple-server.te
less simple-server.fc
less simple-server.if
  • Wir testen diese SELinux Policy indem wir diese übersetzen und ein RPM-Paket erstellen. Das Paket rpm-build wird benötigt um ein RPM-Paket zu bauen
dnf -y install rpm-build
sh simple-server.sh
ls -l
  • Die neue SELinux Policy befindet sich in der Datei simple-server.pp. Dieses neue SELinux Policy-Modul kann nun geladen und aktiviert werden
semodule -i simple-server.pp
  • Die neue Policy wirkt sich nicht auf schon gestartete Prozesse aus. Daher stoppen wir den vorher gestarteten simple-server Prozess
pkill simple-server
  • Wir erstellen eine SystemD Service-Unit für den Server Dienst
$EDITOR /etc/systemd/system/simple-server.service
  • Die Unit-Datei
[Unit]
Description=a simple http server
After=syslog.target network.target

[Service]
ExecStart=/usr/local/bin/simple-server

[Install]
WantedBy=multi-user.target
  • Die neue Systemd-Service-Datei muss mit dem richtigen SELinux Label versehen werden
restorecon -R -v /etc/systemd/system/simple-server.service
  • Systemd Service-Units neu laden und den Simple-Server starten
systemctl daemon-reload
systemctl start simple-server
systemctl enable simple-server
systemctl status simple-server
  • Nun den Dienst benutzen (Mit dem Web-Browser auf Port 8080 zugreifen). Das SELinux Modul ist noch im Permissive Mode, Verstösse gegen die Policy werden im Audit-Log protokolliert
ausearch -m avc -ts recent -c simple-server
  • Auch das Systemd-Journal liefert Fehlermeldungen über SELinux-Troubleshoot Modul setroubleshoot
journalctl | grep setroubleshoot
  • Erklärungen zu den Policy-Fehlermeldungen ausgeben
ausearch -m avc -ts today -c simple-server | audit2why  | less
  • Mittels des Programms audit2allow lassen sich Policy-Regeln aus den Audit-Meldungen erstellen. Diese Regeln sind selten 100% korrekt und müssen oft nachbearbeitet werden, helfen aber enorm bei der Erstellung eines Regelwerkes
    • Der Befehl sepolgen-ifgen erzeugt aus den SLinux Interface-Dateien des Systems Hilfdateien für die Erstellung der Policy-Dateien
    • Der Befehl audit2allow -R gibt die SELinux Policy-Regeln auf dem Terminal aus. Diese bauen wir per copy-n-paste in die Type-Enforcement-Quelldatei simple-server.te ein. Dabei muss auf die korrekte Reihenfolge der Abschnitte geachtet werden (require Block unter Deklarationen, allow Ausdrücke darunter):
sepolgen-ifgen -v
ausearch -m avc -ts today -c simple-server | audit2allow -R
require {
        type simple-server_t;
        class tcp_socket { bind create setopt accept listen };
}

#============= simple-server_t ==============
allow simple-server_t self:tcp_socket { bind create setopt accept listen };
corenet_tcp_bind_generic_node(simple-server_t)
corenet_tcp_bind_http_cache_port(simple-server_t)
  • Neue Policy-Regeln in die Policy einfügen, Modul entfernen, neu kompilieren und dann neu laden
semodule -r simple-server
sh ./simple-server.sh
semodule -i simple-server.pp
systemctl restart simple-server
  • Die Anwendung benutzen und testen, danach wieder das Audit-Log auf SELinux Fehler des simple-server Prozesses prüfen. GGf. neue Regeln erstellen und Modul und Programm neu laden
  • Diese Schritte wiederholen bis keine SELinux Meldungen mehr im Audit-Log auftauchen
    ausearch -m avc -ts recent -c httpd -i
    <no matches>
    
  • Die Anwendung ggf. für eine gewisse Zeit in Produktion im permissive Modus betreiben und auf SELinux Fehler prüfen
  • Treten keine Fehler mehr auf, dann die Zeile permissive simple-server_t; in der Type-Enforcement Datei auskommentieren und das Modul im enforcing Modus betreiben
  • Schalten wir nun das simple-server Modul in den enforcing Modus, werden wir feststellen das das Programm doch nicht wie gewünscht funktioniert
  • Ein Trace des laufenden Programms mittels strace oder eBPF oder bpftrace zeigt das die Syscalls shutdown und write fehlschlagen. Diese werden von SELinux unterbunden, aber nicht an das Audit-Subsystem gemeldet.
  • Aufgabe: Trage die Syscalls shutdown und write in die Policy ein, übersetze die Policy und teste erneut

6.9.4 Die "DoNotAudit" Regeln ausschalten

  • Entwickler von SELinux-Policies können bestimmte Regeln vom Auditing ausschliessen
    • Um übermässiges Logging im Audit zu vermeiden
    • Verstösse gegen SELinux-Regeln, welche mit donotaudit markiert sind, werden nicht im Audit-Log vermerkt
    • Bei der Entwicklung von neuen SELinux Policies kann dies stören, denn hier möchte man im Audit-Log ein möglichst vollständiges Bild aller Verstösse bekommen
    • Die donotaudit Regeln in der SELinux-Richtline ausschalten
      # semodule -DB
      
    • Um die donotaudit Regel wieder zu aktivieren
      # semodule -B
      

6.10 Eine Änderung an einer SELinux Policy

  • Unser "Simple-Server" lernt das Logging in eine Datei. In der Quelldatei-Box sehen wir die Änderungen an dem Quellcode des simple-server.c Programms:
--- simple-server1.c    2022-10-25 09:22:06.684216579 +0000
+++ simple-server2.c    2022-10-26 08:53:33.319698944 +0000
@@ -19,6 +19,13 @@
  int main()
  {
    int one = 1, client_fd;
+   FILE *f = fopen("/var/log/simple-server.log", "a");
+   if (f == NULL)
+   {
+       printf("Error opening file!\n");
+       exit(1);
+   }
+
    struct sockaddr_in svr_addr, cli_addr;
    socklen_t sin_len = sizeof(cli_addr);

@@ -40,7 +47,8 @@
    listen(sock, 5);
    while (1) {
      client_fd = accept(sock, (struct sockaddr *) &cli_addr, &sin_len);
-     printf("got connection\n");
+     fputs("got connection\n",f);
+     fflush(f);

      if (client_fd == -1) {
        perror("Can't accept");
  • Das gepatchte Programm. Dieses Programm schreibt nun ein einfaches Log in die Datei /var/log/simple-server.log. Den Nachfolgenden Quellcode in die Datei simple-server2.c speichern
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <err.h>

char response[] = "HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=UTF-8\r\n\r\n"
"<!DOCTYPE html><html><head><title>Bye-bye baby bye-bye</title>"
"<style>body { background-color: #111 }"
"h1 { font-size:4cm; text-align: center; color: black;"
" text-shadow: 0 0 2mm red}</style></head>"
  "<body><h1>Goodbye, world!</h1></body></html>\r\n";

int main()
{
  int one = 1, client_fd;
  FILE *f = fopen("/var/log/simple-server.log", "a");
  if (f == NULL)
  {
      printf("Error opening file!\n");
      exit(1);
  }

  struct sockaddr_in svr_addr, cli_addr;
  socklen_t sin_len = sizeof(cli_addr);

  int sock = socket(AF_INET, SOCK_STREAM, 0);
  if (sock < 0)
    err(1, "can't open socket");

  setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int));
  int port = 8080;
  svr_addr.sin_family = AF_INET;
  svr_addr.sin_addr.s_addr = INADDR_ANY;
  svr_addr.sin_port = htons(port);

  if (bind(sock, (struct sockaddr *) &svr_addr, sizeof(svr_addr)) == -1) {
    close(sock);
    err(1, "Can't bind");
  }

  listen(sock, 5);
  while (1) {
    client_fd = accept(sock, (struct sockaddr *) &cli_addr, &sin_len);
    fprintf(f,"got connection\n");
    fflush(f);

    if (client_fd == -1) {
      perror("Can't accept");
      continue;
    }

    write(client_fd, response, sizeof(response) - 1); /*-1:'\0'*/
    close(client_fd);
  }
}
  • In der SELinux-Policy-Quelldatei simple-server.fc definieren wir den neuen Datei-Kontext var_log_t für die Log-Datei
/var/log/simple-server.log       --             gen_context(system_u:object_r:var_log_t,s0)
  • Die SELinux Type-Enforcment Datei für simple-server auf Permissive stellen und das SELinux-Modul neu übersetzen und laden
  • Den neuen Server-Dienst übersetzen, den alten simple-server Prozess stoppen, die neue Programm-Datei nach /usr/local/bin kopieren, das SELinux Label anpassen und den Dienst neu starten
gcc -o simple-server simple-server2.c
systemctl stop simple-server
cp simple-server /usr/local/bin
restorecon -R -v /usr/local/bin/simple-server
systemctl start simple-server
  • Per Web-Browser die Webseite auf http://selinuxNNN.linux-sicherheit.org:8080/ aufrufen.
  • Neue SELinux Audit-Meldungen tauchen auf
# ausearch -m avc -ts recent -c simple-server
----
time->Wed Aug 24 21:17:34 2016
type=SYSCALL msg=audit(1472073454.717:1390): arch=c000003e syscall=2 success=yes exit=3 a0=400ae2 a1=441 a2=1b6 a3=21000 items=0 ppid=1 pid=7869 auid=4294967295 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=4294967295 comm="simple-server" exe="/usr/local/bin/simple-server" subj=system_u:system_r:simple-server_t:s0 key=(null)
type=AVC msg=audit(1472073454.717:1390): avc:  denied  { open } for  pid=7869 comm="simple-server" path="/var/log/simple-server.log" dev="vda1" ino=268256 scontext=system_u:system_r:simple-server_t:s0 tcontext=system_u:object_r:var_log_t:s0 tclass=file
type=AVC msg=audit(1472073454.717:1390): avc:  denied  { create } for  pid=7869 comm="simple-server" name="simple-server.log" scontext=system_u:system_r:simple-server_t:s0 tcontext=system_u:object_r:var_log_t:s0 tclass=file
type=AVC msg=audit(1472073454.717:1390): avc:  denied  { add_name } for  pid=7869 comm="simple-server" name="simple-server.log" scontext=system_u:system_r:simple-server_t:s0 tcontext=system_u:object_r:var_log_t:s0 tclass=dir
type=AVC msg=audit(1472073454.717:1390): avc:  denied  { write } for  pid=7869 comm="simple-server" name="log" dev="vda1" ino=258603 scontext=system_u:system_r:simple-server_t:s0 tcontext=system_u:object_r:var_log_t:s0 tclass=dir
  • Die Erweiterungen zur SELinux Policy ausgeben, prüfen und in die Type-Enforcement-Datei simple-server.te einfügen:
 # ausearch -m avc -ts recent -c simple-server | audit2allow -R


require {
        type simple-server_t;
}

#============= simple-server_t ==============
auth_log_filetrans_login_records(simple-server_t)
logging_manage_generic_logs(simple-server_t)
  • simple-server SELinux Modul entfernen, Policy neu übersetzen, Modul neu laden, testen
  • Ggf. fehlen die Regeln um Log-Dateien anlegen und schreiben zu dürfen. Wenn dies der Fall ist, die donotaudit Funktion in der SELinux-Policy deaktivieren
    • Ein Blick in die bestehenden SELinux Policy Quelldateien von ähnlichen Programmen kann (auch) helfen
  • Wir fügen der Type-Enforcement-Quelldatei den Typ var_log_t und die Klassen file (Syscalls create, open und write) und dir (Verzeichnis mit den Syscalls write und add_name) hinzu
require {
        type simple-server_t;
        type var_log_t;
        class tcp_socket { bind create setopt accept listen shutdown write };
        class file { create open write };
        class dir { write add_name };
}
  • Wir fügen der Type-Enforcement-Quelldatei die Regeln für den Zugriff auf Dateien und Verzeichnisse im /var/log Verzeichnisbaum hinzu:
allow simple-server_t var_log_t:file { create open write };
allow simple-server_t var_log_t:dir { write add_name };
  • Das Makro logging_rw_generic_log_dirs erlaubt das Schreiben von Log-Dateien
    logging_rw_generic_log_dirs(simple-server_t)
    
  • Solange die Policy anpassen, bis keine Permission-Meldungen im Audit-Log erscheinen
  • Prüfen, das die Log-Datei korrekt erstellt wird

6.10.1 Lösung - Finale Policy-Datei für "simple-server"

  • Als finalen Schritt den Permissive Modus aus der Policy herausnehmen, Die Versionsnummer anpassen (Version 1.1.0), übersetzen und nochmals testen
policy_module(simple-server, 1.1.0)

########################################
#
# Declarations
#

type simple-server_t;
type simple-server_exec_t;
init_daemon_domain(simple-server_t, simple-server_exec_t)

require {
        type simple-server_t;
        type var_log_t;
        class tcp_socket { accept bind create listen setopt shutdown write };
        class file { create open write };
        class dir { write add_name };
}

#permissive simple-server_t;

########################################
#
# simple-server local policy
#
allow simple-server_t self:fifo_file rw_fifo_file_perms;
allow simple-server_t self:unix_stream_socket create_stream_socket_perms;

allow simple-server_t self:tcp_socket { accept bind create listen setopt shutdown write };
allow simple-server_t var_log_t:file { create open write };
allow simple-server_t var_log_t:dir { create write add_name };

corenet_tcp_bind_generic_node(simple-server_t)
corenet_tcp_bind_http_cache_port(simple-server_t)
domain_use_interactive_fds(simple-server_t)

files_read_etc_files(simple-server_t)

miscfiles_read_localization(simple-server_t)
logging_manage_generic_logs(simple-server_t)
logging_rw_generic_log_dirs(simple-server_t)
  • simple-server SELinux Modul laden, simple-server Prozess per Systemd neu starten, Programm testen

6.11 Hilfsmittel zur Erstellung von SELinux-Policy Regelwerken

  • strace - Kann die benutzten Systemcalls eines laufenden Prozesses ausgeben
  • autrace - Kann die benutzten Systemcalls eines Prozessaufrufs ausgeben
  • nm - kann die Aufrufe in die C-Bibliothek (GLIBC) ausgeben:
# nm /usr/local/bin/simple-server | grep @GLIBC
                U accept@GLIBC_2.2.5
                U bind@GLIBC_2.2.5
                U close@GLIBC_2.2.5
                U err@GLIBC_2.2.5
                U htons@GLIBC_2.2.5
                U __libc_start_main@GLIBC_2.34
                U listen@GLIBC_2.2.5
                U perror@GLIBC_2.2.5
                U puts@GLIBC_2.2.5
                U setsockopt@GLIBC_2.2.5
                U socket@GLIBC_2.2.5
                U write@GLIBC_2.2.5
  • eBPF und bpftrace sind sehr gute Hilfsmittel, um Prozesse und auch das Verhalten von Prozessen unter SELinux zu analysieren

7 Netzwerk-Logging im Audit-Daemon

  • Der Audit-Daemon kann Audit-Loginformationen auch über eine Netzwerk-Verbindug entgegennehmen. Dies erlaubt das Zusammenführen von Audit-Log-Informationen auf einem zentralen Audit-Log-Host. Wichtige forensische Informationen werden ausserhalb des Zugriffs durch Angreifer gespeichert.
  • Auf dem zentralen Log-System wird die Einstellung tcp_listen_port in der Konfigurations-Datei /etc/audit/auditd.conf für den Audit-Daemon aktiviert
tcp_listen_port = 60
tcp_listen_queue = 5
tcp_max_per_addr = 1
tcp_client_ports = 1024-65535
tcp_client_max_idle = 0
  • Nach dem (Neu-)Start des Audit-Daemons ist nun Port 60 für eingehende Audit-Meldungen geöffnet
  # dnf install lsof
  # lsof -Poni :60
COMMAND   PID USER   FD   TYPE DEVICE OFFSET NODE NAME
auditd  60599 root   12u  IPv6 417339    0t0  TCP *:60 (LISTEN)
  • Auf der Maschine des Audit-Senders das Paket audispd-plugins installieren:
# dnf install audispd-plugins
  • Das Remote-Logging wird über die Plugin-Konfiguration unter /etc/audit/plugins.d/au-remote.conf angeschaltet:
# This file controls the audispd data path to the
# remote event logger. This plugin will send events to
# a remote machine (Central Logger).

active = yes
direction = out
path = /sbin/audisp-remote
type = always
#args =
format = string
  • Der Remote-Audit-Log Dienst hat eine eigene Konfigurationsdatei unter /etc/audit/audisp-remote.conf
#
# This file controls the configuration of the audit remote
# logging subsystem, audisp-remote.
#

remote_server =
port = 60
##local_port =
transport = tcp
queue_file = /var/spool/audit/remote.log
mode = immediate
queue_depth = 10240
format = managed
network_retry_time = 1
max_tries_per_record = 3
max_time_per_record = 5
heartbeat_timeout = 0

network_failure_action = stop
disk_low_action = ignore
disk_full_action = warn_once
disk_error_action = warn_once
remote_ending_action = reconnect
generic_error_action = syslog
generic_warning_action = syslog
queue_error_action = stop
overflow_action = syslog
startup_failure_action = warn_once_continue

##krb5_principal =
##krb5_client_name = auditd
##krb5_key_file = /etc/audisp/audisp-remote.key

8 SELinux Ressources

8.1 Audit Subsystem

8.3 Books

9 eBPF (Bonus Inhalt)

9.1 Was ist eBPF, XDP und BCC?

  • eBPF ist die extended Berkeley Packet Filter virtuelle Maschine im Linux Kernel
  • XDP (eXpress Data Path) ist eine eBPF Schnittstelle zum Netzwerkstack
  • BCC ist die BPF Compiler Collection, eine Sammlung von Tools und eBPF Programmen
  • eBPF ist eine Weiterentwicklung der originalen Berkeley Packet Filter Technologie https://en.wikipedia.org/wiki/Berkeley_Packet_Filter

9.2 Die eBPF Idee

  • eBPF erlaubt es dem Benutzer, Programme im Betriebssystem-Kern innerhalb einer Sandbox auszuführen
    • eBPF ermöglicht es, die Funktionen des Betriebssystem-Kerns sicher und effizient zu erweitern, ohne den Quell-Code des Kernels ändern oder Module laden zu müssen
    • eBPF Programme können Netzwerk-Pakete (und andere Datenstrukturen) innerhalb des Linux-Kernels überwachen und verändern
    • eBPF Programme sind keine Kernel Module, es ist nicht notwendig ein Kernel-Entwickler zu sein um eBPF benutzen zu können
      • Wissen über Programmierung in der Sprache "C" ist jedoch von Vorteil

9.3 eBPF

ebpf.png

9.4 eBPF Einsatzgebiete

  • Einsatzgebiete für eBPF
    • Netzwerk Sicherheit (erweiterte Firewall Funktionen)
    • Host / Container Security
    • Forensische Analyse laufender Prozesse
    • Fehlerdiagnose
    • Geschwindigkeitsmessungen
    • Rootkits? Backdoors im Kernel oder in Netzwerkkarten?

9.5 eBPF Verfügbarkeit

9.6 Die Wurzeln von BPF

  • Der originale BSD Packet Filter (BPF) wurde von Steven McCanne und Van Jacobson am Lawrence Berkeley Laboratory entwickelt (https://www.tcpdump.org/papers/bpf-usenix93.pdf)
    • BPF wurde auf fast alle Unix/Linux Systeme und viele non-Unix Betriebssysteme portiert (z.B. Windows, BeOS/Haiku, OS/2 …)
    • BPF ist die Basis-Technologie hinter bekannten Netzwerk-Sniffing Werkzeugen wie tcpdump und Wireshark

9.7 BPF am Beispiel von tcpdump

  • Ein BPF-Filter (z.B. für tcpdump) wird in einen Bytecode für die BPF virtuelle Maschine im Linux-Kernel übersetzt und in den Kernel geladen
    • Das Betriebssystem ruft das BPF-Programm für jedes Netzwerk-Paket auf, welches den Netzwerk-Stack durchläuft
    • Nur Pakete, welche auf den Filter-Ausdruck passen, werden an das Programm im Userspace weitergeleitet (der tcpdump Prozess in dieses Beispiel)
    • BPF reduziert die Menge der Daten, welche zwischen dem Kernel und dem Userspace ausgetauscht werden müssen

9.8 BPF am Beispiel von tcpdump

tcpdump kann angewiesen werden den BPF Quellcode des tcpdump Filters auszugeben:

# tcpdump -d port 53 and host 1.1.1.1
Warning: assuming Ethernet
(000) ldh      [12]
(001) jeq      #0x86dd          jt 19   jf 2
(002) jeq      #0x800           jt 3    jf 19
(003) ldb      [23]
(004) jeq      #0x84            jt 7    jf 5
(005) jeq      #0x6             jt 7    jf 6
(006) jeq      #0x11            jt 7    jf 19
(007) ldh      [20]
(008) jset     #0x1fff          jt 19   jf 9
(009) ldxb     4*([14]&0xf)
(010) ldh      [x + 14]
(011) jeq      #0x35            jt 14   jf 12
(012) ldh      [x + 16]
(013) jeq      #0x35            jt 14   jf 19
(014) ld       [26]
(015) jeq      #0x1010101       jt 18   jf 16
(016) ld       [30]
(017) jeq      #0x1010101       jt 18   jf 19
(018) ret      #262144
(019) ret      #0

9.9 eBPF vs. BPF

  • Während BPF (heute auch cBPF = classic BPF genannt) Netzwerk Pakete im Betriebssystem-Kern filtert, kann eBPF auf weitere Kernel-Datenstrukturen Filter anwenden und Programm-Code ausführen:
    • Kernel Systemcalls
    • Kernel Tracepoints
    • Kernel Funktionen
    • Userspace Tracepoints
    • Userspace Funktionen

9.10 eBPF und der Linux Kernel

  • Die erste Version von eBPF wurde im Linux Kernel 3.18 eingeführt
    • Neue Kernel Versionen bringen weitere, neue eBPF Funktionen
    • Linux Distributionen (Red Hat/Canonical/Suse) haben zum Teil eBPF Funktionen auf ältere LTS/EL Kernel Versionen zurückportiert
    • Eine Übersicht der eBPF Funktionen nach Linux Kernel Version aufgeschlüsselt: https://github.com/iovisor/bcc/blob/master/docs/kernel-versions.md

9.11 Die eBPF Architektur

9.11.1 Die eBPF virtuelle Maschine

  • eBPF Programme werden für eine virtuelle CPU Architektur übersetzt
  • Der Programmcode wird in den Linux Kernel geladen und dort geprüft
  • Auf populären CPU Architekturen (amd64, AARCH64) wird der eBPF Bytecode in nativen Maschinencode re-compiliert (Just in Time Compiler = JIT)

9.11.2 XDP - Express Data Path

  • Der express data path (XDP) innerhalb des Linux-Kernels ist eine Infrastruktur, um auf unterster Ebene Kontrolle über Netzwerk-Pakete auszuüben
    • Der normale Datenfluss im Linux Netzwerk-Stack kann via XDP umgangen werden
    • eBPF Programme können in den eXpress Data Path (XDP) geladen werden

9.11.3 XDP / eBPF Hardware Offloading

  • XDP eBPF Programm können auf verschiedenen Ebenen in den Linux Kernel geladen werden
    • Offload XDP: direkt in die Netzwerk-Hardware (ASIC/FPGA, benötigt Unterstützung für XDP in der Hardware, z.B. vorhanden in den Netronome Netzwerkadaptern)
    • Native XDP: In den Linux Kernel Netzwerk-Treiber der Netzwerkschnittstelle (benötigt Unterstützung durch den Treiber)
    • Generic XDP: In den Linux Kernel Netzwerk-Stack (weniger Performance, aber ohne besondere Unterstützung von Hardware oder Treibern möglich)

9.11.4 XDP / eBPF Ausführungs-Ebenen

xdp-ebpf-level.png

9.11.5 XDP Funktionen

  • XDP Programme können
    • lesen: Netzwerk-Pakete und Statistiken sammeln
    • verändern: Den Inhalt der Netzwerkpakete ändern
    • verwerfen: Ausgewählte Netzwerk-Pakete können verworfen werden (Firewall)
    • umleiten: Netzwerkpakete können auf die gleichen oder andere Netzwerkschnittstellen umgeleitet werden (Switching/Routing)
    • durchlassen: Das Netzwerkpaket wird an den Linux TCP/IP Stack zur normalen Bearbeitung übergeben

9.11.6 XDP vs DDoS Angriffe

  • XDP kann unerwünschten Netzwerk-Verkehr schon sehr früh im Netzwerk-Stack verwerfen (z.B. innerhalb der Netzwerk-Hardware). Dies kann zum Schutz gegen DDoS Angriffe eingesetzt werden

ebpf-xdf-ddos-twitter.png

9.12 eBPF "Real-World" Anwendungen

9.12.1 eBPF/XDP Support in DNS Software

  • Der Open-Source DNS Load-Balancer DNSdist von PowerDNS kann DNS Pakete via eBPF und XDP filtern oder per Rate-Limiting beschränken
  • Der Knot Resolver (seit Version 5.2.0) kann mittels ePBF und XDP den Linux TCP/IP Stack für DNS Pakete umgehen und die DNS-Pakete direkt an den Knot DNS Resolver Prozess im Userspace weiterleiten (https://knot-resolver.readthedocs.io/en/stable/daemon-bindings-net_xdpsrv.html). Hierdurch wird eine enorme Geschwindigkeitssteigerung der DNS Antwortrate erziehlt.

9.12.2 eBPF Programme zur Analyse des Laufzeitverhalten von Linux-Systemen

bcc_tracing_tools_2019.png

9.12.3 Beispiel: eBPF Syscall Auswertung

  • Die Syscalls eines BIND 9 Prozesses auswerten mit dem Programm syscount
# syscount-bpfcc -p `pgrep named` -i 10
Tracing syscalls, printing top 10... Ctrl+C to quit.
[07:34:19]
SYSCALL                   COUNT
futex                       547
getpid                      121
sendto                      113
read                         56
write                        31
epoll_wait                   31
openat                       23
close                        20
epoll_ctl                    20
recvmsg                      20

9.12.4 Beispiel: eBPF Prozess Capabilities

  • Die Linux Capabilities von laufenden Prozessen anzeigen
# capable-bpfcc | grep named
07:36:17  0      29378  (named)          24   CAP_SYS_RESOURCE     1
07:36:17  0      29378  (named)          24   CAP_SYS_RESOURCE     1
07:36:17  0      29378  (named)          12   CAP_NET_ADMIN        1
07:36:17  0      29378  (named)          21   CAP_SYS_ADMIN        1
07:36:17  0      29378  named            6    CAP_SETGID           1
07:36:17  0      29378  named            6    CAP_SETGID           1
07:36:17  0      29378  named            7    CAP_SETUID           1
07:36:17  109    29378  named            24   CAP_SYS_RESOURCE     1

9.12.5 Beispiel: gethostlatency

  • Das BCC Programm gethostlatency misst die Latenz der client-seitigen DNS Namensauflösung durch Systemaufrufe wie getaddrinfo oder gethostbyname
# gethostlatency-bpfcc
TIME      PID    COMM                  LATms HOST
10:21:58  19183  ping                 143.22 example.org
10:22:18  19184  ssh                    0.03 host.example.de
10:22:18  19184  ssh                   60.59 host.example.de
10:22:35  19185  ping                  23.44 isc.org
10:22:49  19186  ping                4459.72 yahoo.co.kr

9.12.6 Sicherheitsanwendungen

  • Audit-Frameworks für Linux benutzen eBPF um Prozesse auf einem Host oder im Container ganzheitlich zu überwachen:
    • Dateisystemzugriffe
    • Netzwerkverkehr
    • Prozess-Execution
    • Systemcalls

9.12.7 Sicherheitsanwendungen

  • Dabei wird jedem Prozess eine execution-id zugewiesen und alle Log-Einträge können mittels der ID einem Prozess zugeordnet werden
  • eBPF kann direkt Policy-Entscheidungen von Linux-Security-Modulen (LSMs wie SELinux, AppArmor, Tomoya) anpassen
    • eBPF kann direkt Dateizugriffe, Netzwerk-Kommunikation oder Syscalls unterbinden

9.12.8 Sicherheitsanwendungen: Tetragon

tetragon.png

9.12.9 Sicherheitsanwendungen: Falco

falco.png

9.12.10 Sicherheitsanwendungen: tracee

tracee.png

9.13 Quis custodiet ipsos custodes?

9.13.1 Backdoors

  • Mittels eBPF lassen sich potente Backdoors im Betriebssystem-Kernel verstecken
    • Persistent?
    • In der Netzwerk-Karte?
    • Um eBPF Programme in den Kernel zu laden werden priviligierte Rechte (CAPSYSADMIN) benötigt (in neueren Linux Distributions-Versionen)
      • /proc/sys/kernel/unprivileged_bpf_disabled

9.13.2 boopkit

boopkit.png

9.13.3 TripeCross

TripeCross.png

9.13.4 BPFdoor

BPFdoor.png

9.13.5 Symbiote

Symbiote.png

9.13.6 Bvp47

Bvp47.png

9.14 eBPF einschränken

9.14.1 "Unprivilegded eBPF" ausschalten

  • Bis 2022 hatten einige (populäre) Linux Distributionen unpriviligiertes eBPF aktiviert
    • Jeder Benutzer konnte eBPF Code in den Kernel laden!
    • Ubuntu (und andere Linux-Distributionen) haben diese Lücke in Frühjahr 2022 geschlossen
    • Prüfen, daß die Linux Kernel-Variable (sysctl) kernel.unprivileged_bpf_disabled > 0 ist

9.14.2 KPROBEOVERRIDE im Kernel

  • Ist in der Kernel Konfiguration der Schalter CONFIG_BPF_KPROBE_OVERRIDE aktiv (zur Übersetzungszeit des Kernels), so können eBPF Programme die Rückgabewerte von (Kernel-)Funktionen überschreiben
  • Diese Konfiguration sollte in Kernel in Produktions-Umgebungen nicht gesetzt sein (Kernel "config" prüfen)

9.14.3 KProbes ausschalten

  • Durch schreiben des Wertes 0 in die Pseudo-Datei /sys/kernel/debug/kprobes/enabled werden die Kernel-Probes (KPROBES) ausgeschaltet
  • Achtung: ein Benutzer mit Schreibrechten auf diese Datei (z.B. root) kann die KPROBES wieder anschalten

9.14.4 Kernel ohne eBPF

  • In sicherheitskritischen Bereichen sollten ggf. Kernel ohne eBPF Funktion (kprobes, XDP, eBPF TC Filter) eingesetzt werden
    • Hierzu muss der Kernel neu übersetzt werden (dies ist oft nicht praktikabel)
    • Es gibt (derzeit, 2023) keinen Kernel Commandline Schalter um eBPF auszuschalten

9.15 eBPF Literatur

9.15.1 Buch: Linux Observability with BPF

Von David Calavera, Lorenzo Fontana (November 2019)

book1.png

9.15.2 Buch: Systems Performance (2nd ed.)

Von Brendan Gregg (Dezember 2020)

book2.jpg

9.15.3 Buch: BPF Performance Tools

Von Brendan Gregg (Dezember 2019)

book3.jpg

9.15.4 Report: What is eBPF? (O'Reilly)

What Is eBPF.png

9.15.5 Report: Security Observability with eBPF (O'Reilly)

Security Observability with eBPF.png

9.16 eBPF Links

9.16.1 eBPF

9.16.2 eBPF

9.16.3 BCC

9.16.6 eBPF Prometheus exporter

9.16.8 eXpress Data Path (XDP)

9.16.9 BPF Backdoor

9.16.10 BPF Malware/Angriffe

9.16.11 BPF Malware/Angriffe

9.16.12 eBPF Sicherheit

9.16.13 eBPF Sicherheit

9.16.14 eBPF Sicherheitsprobleme

  • CVE-2021-33624 kernel: Linux kernel BPF protection against speculative execution attacks can be bypassed to read arbitrary kernel memory

9.16.15 eBPF Sicherheitswerkzeuge

9.16.16 eBPF Audit-Tools

9.17 eBPF Praxis

9.17.1 Discover eBPF programs with bpftool

  • Installing bpftool:
% dnf install bpftool
  • Which eBPF programms are loaded into the Linux Kernel?
% bpftool prog list
306: cgroup_device  tag ca8e50a3c7fb034b  gpl
        loaded_at 2023-02-09T21:16:16+0000  uid 0
        xlated 496B  jited 307B  memlock 4096B
        pids systemd(1)
307: cgroup_skb  tag 6deef7357e7b4530  gpl
        loaded_at 2023-02-09T21:16:16+0000  uid 0
        xlated 64B  jited 54B  memlock 4096B
        pids systemd(1)
308: cgroup_skb  tag 6deef7357e7b4530  gpl
        loaded_at 2023-02-09T21:16:16+0000  uid 0
[...]
% sysctl kernel.bpf_stats_enabled
kernel.bpf_stats_enabled = 0
  • Let's enable eBPF statistics
% sysctl -w kernel.bpf_stats_enabled=1
  • Dump disassembled eBPF source code
% # bpftool prog dump xlated id 317
   0: (bf) r6 = r1
   1: (69) r7 = *(u16 *)(r6 +176)
   2: (b4) w8 = 0
   3: (44) w8 |= 2
   4: (b7) r0 = 1
   5: (55) if r8 != 0x2 goto pc+1
   6: (b7) r0 = 0
   7: (95) exit
  • See the eBPF JIT compiler generated machine code disassembly
% bpftool prog dump jited tag tag 6deef7357e7b4530
317: cgroup_skb  tag 6deef7357e7b4530  gpl                                                                                                      [6/723]
0xffffffffc0af90d0:
   0:   nopl   0x0(%rax,%rax,1)
   5:   xchg   %ax,%ax
   7:   push   %rbp
   8:   mov    %rsp,%rbp
   b:   push   %rbx
   c:   push   %r13
   e:   push   %r14
  10:   mov    %rdi,%rbx
  13:   movzwq 0xb0(%rbx),%r13
[...]

9.17.2 bpttrace tools and on-liners

  • Install the BIND 9 DNS Server
% dnf install bind
% systemctl enable --now named
  • Install the bpftrace tools
% dnf install bpftrace
  • Switch the operating system to use the BIND 9 DNS resolver
% echo "nameserver 127.0.0.1" > /etc/resolv.conf
  • Example 1: Start gethostlatency.bt (found in /usr/share/bpftrace/tools) in one terminal, execute some other network tools that require DNS name resolution (ping, dnf, traceroute etc) to see the DNS name resolution latency
  • Example 2: Count UDP send/receives by on-CPU PID and process name (Script is an excerpt From "BPF Performance Tools" by Brendan Gregg)
    • Execute some DNS queries against the BIND 9 resolver
    • Exec the bpftrace script with CTRL+C to see the report
% bpftrace -e "k:udp*_sendmsg,k:udp*_recvmsg { @[func, pid, comm] = count(); }"
  • Example 3: Show packets received over UDP by the named process as a histogram (execute some queries, then terminate the script with CTRL+C)
% bpftrace -e "kr:udp_recvmsg /pid == $(pgrep named)/ { @recv_bytes = hist(retval); }"
  • Example 4: Histogram of UDP packets send by the BIND 9 process (execute some queries, then terminate the script with CTRL+C)
% bpftrace -e "kprobe:udp_sendmsg /pid == $(pgrep named)/ { @size = hist(arg2); }"
  • Example 5: BIND 9 tracing - print whenever rndc status is executed
% bpftrace -e 'uprobe:named:named_server_status { print("rndc status executed") }'
  • Example 6: How long does it take (in nanoseconds) to flush the cache with rndc flush? Do some queries against the BIND 9 DNS resolver to fill the cache. Then call rndc flush to flush the cache. The printed value should get higher the more cache entries need to be cleaned.
% bpftrace -e "uprobe:/usr/sbin/named:named_server_flushcache / pid == $(pgrep named) /{ @start[tid] = nsecs; }
    uretprobe:/usr/sbin/named:named_server_flushcache /@start[tid]/ { print(nsecs - @start[tid]); delete(@start[tid]); }"
  • Trace the cache related functions BIND 9 executes. Send some queries to the BIND 9 DNS resolver, then use rndc flush, rndc flushname <domain-name> and rndc flushtree <name> and see which functions are executed inside BIND 9
% bpftrace -e 'uprobe:/usr/lib64/libdns-9.16.23-RH.so:dns_cache* { print(func) }'

9.17.3 Instrumenting BIND 9

  • Configure BIND 9 to forward all request for the domain isc.org to Quad9 (9.9.9.9). In the file /etc/named.conf add
zone "isc.org" {
  type forward;
  forwarders { 9.9.9.9; };
};
  • Check the configuration with named-checkconf and restart the BIND 9 DNS server with systemctl restart named
  • Install bpftrace
dnf install bpftrace
  • Save the following source into the file forward-trace.bt
#!/usr/bin/bpftrace

struct dns_name {
        unsigned int   magic;
        unsigned char *ndata;
        unsigned int   length;
        unsigned int   labels;
        unsigned int   attributes;
        unsigned char *offsets;
//      isc_buffer_t  *buffer;
//      ISC_LINK(dns_name_t) link;
//      ISC_LIST(dns_rdataset_t) list;
};

BEGIN
{
  print("Waiting for forward decision...\n");
}
uprobe:/usr/lib64/libdns-9.16.23-RH.so:dns_fwdtable_find
{
  @dns_name[tid] = ((struct dns_name *)arg1)->ndata
}

uretprobe:/usr/lib64/libdns-9.16.23-RH.so:dns_fwdtable_find
{
 if (retval == 0) {
    printf("Forwarded domain name: %s\n", str(@dns_name[tid]));
 }
 delete(@dns_name[tid]);
}
  • Make the file executeable
% chmod +x forward-trace.bt
  • Execute the bpftrace script
% ./forward-trace.bt
  • Login to the lab machine from a different terminal (or use tmux). Flush the cache of the BIND 9 resolver with rndc flush, then execute a DNS name resolution for isc.org and for other domains. See that the bpftrace script reports the forward decisions in BIND 9
% dig @localhost isc.org
  • Our eBPF program in the bpftool listing
bpftool prog list | tail
        xlated 64B  jited 54B  memlock 4096B
        pids systemd(1)
383: kprobe  name uretprobe__dns_  tag 30ca488d1d146cd7  gpl run_time_ns 297757 run_cnt 115
        loaded_at 2023-02-09T21:46:24+0000  uid 0
        xlated 536B  jited 314B  memlock 4096B  map_ids 25,26
        pids forward-trace.b(34273)
384: kprobe  name uprobe__dns_fwd  tag c30da15c9fd45317  gpl run_time_ns 116583 run_cnt 115
        loaded_at 2023-02-09T21:46:24+0000  uid 0
        xlated 176B  jited 105B  memlock 4096B  map_ids 25
        pids forward-trace.b(34273)
  • Try to disassemble the eBPF programm (eBPF code and JIT)

9.17.4 Using XDP/eBPF to drop all UDP except DNS

  • Install the Kernel headers for the running Linux Kernel. These headers are needed to get the function names and entry points into the Linux Kernel for the eBPF probes
    % dnf install kernel-headers-$(uname -r)
    
  • Create a text file with the name drop-non-dns-udp.c and enter the following C-Code. This eBPF program will …
    • … be executed for every incoming network packet
    • … prints the text got a packet whenever an IP packet is received
    • … extracts the IP- and UDP-header from the packet
    • … if the packet neither has the UDP source port of 53 (DNS) nor a destination port of 53 the return code of XDP_DROP will be returned, which will discard the packet. A message is printed to inform about the dropped packet
    • … all other network packets will be given with the return value of XDP_PASS into the Linux TCP/IP stack for further processing
    • This is an example XDP program. Production quality eBPF/XDP programms should use the eBPF map structures to exchange information with the User-Space program instead of using the bpf_trace_printk function.
#define KBUILD_MODNAME "filter"
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/in.h>
#include <linux/udp.h>

int udpfilter(struct xdp_md *ctx) {
  bpf_trace_printk("got a packet\n");
  void *data = (void *)(long)ctx->data;
  void *data_end = (void *)(long)ctx->data_end;
  struct ethhdr *eth = data;
  if ((void*)eth + sizeof(*eth) <= data_end) {
    struct iphdr *ip = data + sizeof(*eth);
    if ((void*)ip + sizeof(*ip) <= data_end) {
      if (ip->protocol == IPPROTO_UDP) {
        struct udphdr *udp = (void*)ip + sizeof(*ip);
        if ((void*)udp + sizeof(*udp) <= data_end) {
          if ((udp->dest != ntohs(53)) && (udp->source != ntohs(53))) {
            if (udp->dest != udp->source) {
              bpf_trace_printk("drop udp src/dest port %d/%d\n", ntohs(udp->source), ntohs(udp->dest));
              return XDP_DROP;
            }
          }
        }
      }
    }
  }
  return XDP_PASS;
}
  • Also create a eBPF loader program (written in Python) in the file drop-non-dns-udp.py.
    • This loader will compile the C-Program above into eBPF byte code, load the program into the Linux Kernel and will detach the eBPF program with the loopback network interface (lo)
    • There will be a few warnings issued during compilation. This is not a problem for this exercise and we can ignore these for this lab session
    • The virtio network driver of the virtual machine environment doas not allow eBPF programs on the regular network interfaces (eth0 and eth1), therefore we test this function with the lookback interface.
#!/usr/bin/env python3

from bcc import BPF
import time

device = "lo"
b = BPF(src_file="drop-non-dns-udp.c")
fn = b.load_func("udpfilter", BPF.XDP)
b.attach_xdp(device, fn, 0)

try:
  b.trace_print()
except KeyboardInterrupt:
  pass

b.remove_xdp(device, 0)
  • Make the loader executable
% chmod +x drop-non-dns-udp.py
  • Execute the loader program (the warnings can be ingnored)
% ./drop-non-dns-udp.py
  • Create a new connection to the lab machines (new browser tab, another SSH connection or use tmux)
  • Create DNS queries towards the running BIND 9 DNS resolver over the loopback interface. These queries shoul not be blocked:
% dig @localhost isc.org
  • Send UDP packets (DNS or other protocol) towards a port other than 53, the packets will be discarded before entering the regular Linux Kernel TCP/IP stack:
% dig -p 5353 @localhost isc.org