„Eine Kette ist nur so stark wie ihr schwächstes Glied“. Dieses Sprichwort ist auch in der IT-Security anwendbar. Die sicherste Applikation kann zu Fall kommen, wenn der betreibende Server Schwachstellen aufweist.
In diesem Artikel wollen wir eine Basis-Absicherung für den eigenen (*nix) Server behandeln, unabhängig von dem Verwendungszweck und der darauf laufenden Applikation.
Der Prozess, einen Server abzusichern, wird auch Server Hardening genannt und verbessert die Infrastruktursicherheit. Das Ziel ist es, es einem Angreifer so schwer wie möglich zu machen, einen Server erfolgreich zu attackieren. Gerade für Betreiber von Cloud-Plattformen oder von Anbietern von sonstigen öffentlichen Portalen ist dies ein wichtiges Thema.
Wir haben die Basis-Absicherung in drei Hauptpunkte unterteilt. Reduzierung der Angriffsfläche, Absicherung des Zugriffs und abrundend Kontrolle und Sicherung. Nachdem diese Maßnahmen durchgeführt worden sind, betrachten wir den Beispielserver aus der Sicht des Angreifers. Im ersten Teil der beiden Artikel wird die Reduzierung der Angriffsfläche behandelt, die weiteren Punkte sind Teil des zweiten Artikels.
Diese Basis-Absicherung wollen wir ohne kommerzielle Software von Drittanbietern oder zusätzliche Hardware umsetzen, um den Prozess möglichst wirtschaftlich zu gestalten.
Alle genannten Maßnahmen wurden auf einem beispielhaften Server umgesetzt. Unser Beispielserver hat die Aufgabe eines Webservers, einfachheitshalber übernimmt diese Rolle Python.
Der auf dem Server betriebene Dienst ist grundsätzlich jedoch egal, da die Basis-Absicherung nicht dienstspezifisch stattfindet. Für das Beispiel wird dieser betrieben, um einen Dienst zu repräsentieren, der öffentlich zugänglich sein muss.
Unser Beispielserver ist ein Standard Ubuntu Server LTS (16.04.3) und betreibt einen Python3 HTTP Server auf Port 8000. Für demonstrative Zwecke wurde auch der NGINX Web-Server sowie der vsftpd FTP-Server installiert. Die folgenden Maßnahmen sind für diesen Beispielserver zugeschnitten, sollten aber auf jedem auf Debian/Ubuntu basierenden System gültig sein. Für alternative Betriebssysteme sind die Konzepte gleichermaßen umsetzbar, aber die Befehle müssen angepasst werden.
Angriffsfläche
Um es einen Angreifer möglichst schwer zu machen, ist einer der wichtigsten Punkte, die Angriffsfläche zu minimieren. Mit weniger Zielen sinken dementsprechend die Erfolgsaussichten für Angreifer.
Firewall
Der erste Schritt, um die Angriffsfläche zu minimieren, ist es, nicht essentielle, offene Ports auf Infrastrukturebene zu schließen. Eine Übersicht der aktuell lauschenden Ports auf dem Server kann mit dem folgenden Kommando ausgegeben werden.
netstat -tulpn
Mit diesen Informationen ist erkenntlich, dass auf unserem Server Python3 auf TCP Port 8000 (IPv4), SSH auf TCP Port 22 (IPv4 und IPv6) und auf UDP Port 68 (IPv4) ein DHCP-Client lauschen. Außerdem lauschen NGINX auf TCP Port 80 (IPv4 und IPv6) sowie vsftpd auf TCP Port 21 (IPv4 und IPv6).
Python3 ist unser Hauptdienst, der verfügbar sein muss. SSH ist der Dienst, der den externen Zugriff auf den Server erlaubt. Dieser Dienst wird in einem späteren Schritt noch abgesichert, muss aber vorerst weiter erreichbar sein. Der DHCP-Client ist in unserem Fall auch notwendig, da der Server seine IP über DHCP bezieht. Die Dienste NGINX und vsftpd haben geöffnete Ports und sollten eigentlich nicht öffentlich verfügbar sein.
Wir wollen explizit nur die essentiellen Ports nach außen geöffnet haben. Mit einer Firewall ist dies schnell und einfach erreichbar. Wir verwenden hierfür das vorinstallierte Tool iptables, zukünftig wird der Umstieg auf nftables geraten.
Mit den folgenden Kommandos konfigurieren wir die Firewall so, dass alle Ports von außen geschlossen sind. Die Ports, die noch offen bleiben dürfen, müssen explizit angegeben werden. Verbindungen, die vom Server selbst kommen, sollen aber auf allen Ports erlaubt werden. Außerdem sollen bereits aufgebaute Verbindungen ebenso erlaubt werden. Das ist erforderlich, damit Verbindungen, die von unserem Server nach außen aufgebaut wurden, auch wieder eingehend erlaubt werden. Diese Regeln werden für IPv4 und IPv6 den Diensten entsprechend geltend gemacht. Die folgenden Kommandos müssen als root-Benutzer ausgeführt werden:
#Löscht alle aktuell geltenden Regeln iptables -F FORWARD iptables -F INPUT iptables -F OUTPUT ip6tables -F FORWARD ip6tables -F INPUT ip6tables -F OUTPUT #Definiert das Standartverhalten für eingehende oder weitergeleitete Verbindungen (DROP) iptables -P FORWARD DROP iptables -P INPUT DROP ip6tables -P FORWARD DROP ip6tables -P INPUT DROP #Erlaubt standardmäßig ausgehende Verbindungen #Das ist zwar nicht die sicherste Variante, aber ausgehende Regeln können später noch hinzugefügt werden iptables -P OUTPUT ACCEPT ip6tables -P OUTPUT ACCEPT #Erlaubt SSH eingehend iptables -A INPUT -m state --state NEW -p tcp --dport 22 -j ACCEPT ip6tables -A INPUT -m state --state NEW -p tcp --dport 22 -j ACCEPT #Erlaubt ICMP Pakete für Pings etc. iptables -A INPUT -m state --state NEW -p icmp -j ACCEPT ip6tables -A INPUT -m state --state NEW -p ipv6-icmp -j ACCEPT #Erlaubt Port 8000 eingehend für IPv4 iptables -A INPUT -m state --state NEW -p tcp --dport 8000 -j ACCEPT #Erlaubt bestehende Verbindungen iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT ip6tables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
Diese Firewall-Regeln können jetzt mit den jeweiligen Befehlen für IPv4 und IPv6 angezeigt werden.
iptables -L […] ip6tables -L […]
Die Konfiguration der Firewall ist somit jedoch nur temporär und würde nach einem Neustart nicht wieder umgesetzt werden. Die jeweiligen IPv4 und IPv6 Firewall-Regeln können mit den folgenden Befehlen gespeichert und wiederhergestellt werden. Der Befehl zum Wiederherstellen könnte also automatisch beim Systemstart ausgeführt werden, um die Regeln persistent zu machen.
# IPv4 und IPv6 Firewall-Regeln speichern iptables-save > ipv4_regeln.iptables ip6tables-save > ipv6_regeln.iptables # IPv4 und IPv6 Firewall-Regeln wiederherstellen iptables-restore < ipv4_regeln.iptables ip6tables-restore < ipv6_regeln.iptables
Vereinfacht wird die persistente Umsetzung mit dem Paket „iptables-persistent“, das direkt nach der Installation anbietet, die aktuelle IPv4 und IPv6 Firewall-Konfiguration persistent zu speichern. Dieser Dialog kann auch mit dem folgenden Befehl manuell nach der Installation aufgerufen und jederzeit wiederholt werden.
apt install iptables-persistent […] dpkg-reconfigure iptables-persistent
Auch nach einem Neustart sind die definierten Firewall-Regeln somit aktiv.
Zusätzliche Ports können auf gleiche Weise freigeschaltet werden. Hierfür müssen neue Regeln mit dem Protokoll und dem Port definiert werden. Um Änderungen an den Regeln der Firewall persistent zu machen, kann „iptables-persistent“ mit dem oben genannten Kommando neu konfiguriert werden.
iptables -A INPUT -m state --state NEW -p PROTOKOLL --dport PORT -j ACCEPT
Dienste
Der hier frei übersetzte Auszug aus der UNIX-Philosophie „Schreibe Programme, die nur eine Aufgabe ausführen, diese jedoch gut“ sollte auch für Server umgesetzt werden. Je mehr Aufgaben ein Server übernimmt, umso mehr Glieder hat die Kette und umso höher stehen die Chancen, dass eines dieser Glieder bricht.
Dieser Grundsatz ist nicht immer komplett umsetzbar, aber man sollte sich daran orientieren.
Eine Übersicht über alle Dienste, die beim Hochfahren starten, kann mit dem folgenden Kommando ausgegeben werden.
systemctl list-unit-files --state=enabled
In dieser Liste findet man fast alle Dienste wieder, die bei der vorhergehenden Untersuchung der offenen Ports aufgefallen sind. Der Python3 Web-Server wird hier nicht erwähnt, da dieser von uns manuell gestartet wurde und nicht als Dienst registriert ist.
Unnötige Dienste sollten deaktiviert werden, sodass diese erst gar nicht mit dem System starten und eventuelle Angriffsfläche bieten. Das Deaktivieren von Diensten sollte jedoch vorsichtig vorgenommen werden, so könnten z.B. deaktivierte Systemdienste zu Instabilität oder anderen Problemen führen. Dienste können mit dem folgenden Befehl deaktiviert werden. Natürlich könnten diese Programme bei Nichtverwendung auch einfach deinstalliert werden.
systemctl disable <ziel>
Um also die Dienste NGINX und vsftpd zu deaktivieren, werden die folgenden Befehle verwendet:
systemctl disable nginx.service systemctl disable vsftpd.service
Nach einem Neustart werden diese Dienste nicht mehr automatisch gestartet und die Angriffsoberfläche ist verringert.
Updates
Updates für das Betriebssystem und installierte Applikationen sind essentiell. Durch die stetige Weiterentwicklung und Forschung ergeben sich immer wieder neue Sicherheitslücken, die durch Updates behoben werden.
Aktuelle Sicheheitsupdates sind absolut notwendig, um vor neu entdeckten Schwachstellen zu schützen. Diese können manuell oder aber automatisiert installiert werden.
Um der Vergesslichkeit entgegenzuwirken, kann das Tool „unattended-upgrades“ verwendet werden. Hiermit können genau definierte, automatisierte Updates durchgeführt werden.
Automatische Updates mit diesem Tool werden über zwei Konfigurationsdateien gesteuert.
In der Datei „/etc/apt/apt.conf.d/50unattended-upgrades“ wird u.a. definiert, welche Art von Updates installiert werden. Hier können u.a. auch Pakete definiert werden, die nicht automatisch aktualisiert werden sollen. Wenn nur Sicherheitsupdates automatisch installiert werden sollen und sonst keine genauere Konfiguration nötig ist, sieht diese Datei folgendermaßen aus.
# cat /etc/apt/apt.conf.d/50unattended-upgrades Unattended-Upgrade::Allowed-Origins { "${distro_id}:${distro_codename}"; "${distro_id}:${distro_codename}-security"; "${distro_id}ESM:${distro_codename}"; }; Unattended-Upgrade::Package-Blacklist { };
Mit der Datei „/etc/apt/apt.conf.d/20auto-upgrades“ wird das automatische Updaten dann aktiviert und die Intervalle definiert. Beispielsweise kann mit der folgenden Konfiguration definiert werden, dass täglich vorhandene Updates gemacht werden und nicht mehr benötigte Pakete wöchentlich entfernt werden.
# cat /etc/apt/apt.conf.d/20auto-upgrades APT::Periodic::Update-Package-Lists "1"; APT::Periodic::Download-Upgradeable-Packages "1"; APT::Periodic::AutocleanInterval "7"; APT::Periodic::Unattended-Upgrade "1";
Auch wenn somit sicherheitskritische Updates automatisiert installiert werden, ist es nötig, Kontrolle über das System zu bewahren. Die Log-Dateien für das automatische Updatesystem befinden sich unter „/var/log/unattended-upgrades“. Mehr über die Kontrolle können Sie im zweiten Teil des Blogartikels lesen.
Applikationen, die nicht über den Paketmanager des Betriebssystems installiert wurden, müssen ebenso aktualisiert werden. Abhängig von der Applikation müssen Komponenten manuell aktualisiert werden. Hierzu zählen beispielsweise WordPress-Plugins.
Im zweiten Artikel erwarten Sie die Themen Zugriff, Kontrolle und Sicherung sowie ein kurzer Ausschnitt über den Blickwinkel des Angreifers.