Containerisierung auf Nummer Sicher Schotten dicht um Docker

Sicher „eingedock(er)t“? Die legendäre Simplizität von Containern erkaufen sich viele Nutzer durch reihenweise Sicherheitsvorfälle. Zum Glück geht es auch anders. Praxiserprobte Tipps und Tricks zur sicheren „Containerisierung“ von Arbeitslasten verhelfen Docker in ruhige Gewässer.

Anbieter zum Thema

Docker-Container „von der Stange“ sind eine Einladung zu Raubüberfällen. Zum Glück geht es auch anders: mit bewährten Best-Practices zum sicheren „Ein-Docken“ von Arbeitslasten.
Docker-Container „von der Stange“ sind eine Einladung zu Raubüberfällen. Zum Glück geht es auch anders: mit bewährten Best-Practices zum sicheren „Ein-Docken“ von Arbeitslasten.
(© nespix - stock.adobe.com)

Docker wirkt beinahe wie ein Magnet für Angriffe aus dem Cyberspace. Das Absichern einer Docker-Umgebung erfordert sowohl eine gehörige Dosis gesunden Menschenverstands als auch eine Menge administrativer Kleinarbeit.

Container unter Beschuss? Die Anatomie einer Attacke

Hacker zielen besonders gerne auf Docker-Server ab, die durch Konfigurationsfehler „glänzen“. Eine aktuelle Kampagne, die sich durch Crypto-Mining mit Hilfe der kompromittierten Systeme finanziert, haben Forscher von Trend Micro untersucht.

Die Angreifer missbrauchen die REST-API von Docker, um eigene Container auf einem verwundbaren Server einzurichten. Der betroffene Host bezieht ein bösartiges Docker-Image von Docker Hub und initialisiert damit einen neuen Container. Dieser macht dann den Rest von selbst mit Tools wie ZMap, Container-Escape-Skripten, Rootkits, Credential-Stealern und zu guter Letzt Coin-Minern.

Bei den Cyber-Dieben klingelt die Kasse, während sich die betroffene Organisation über die astronomische Rechnung für die eigene Cloud-Infrastruktur oder die ebenso hohe „an-den-Anschlag“-Auslastung der eigenen Hardware wundert.

„Das kann bei uns nie passieren“. Wirklich? Im Spätsommer 2021 haben Sicherheitsforscher fünf bösartige Docker-Container-Images auf Docker Hub entdeckt – Haare sträubend, wenn man die reelle Verbreitung der resultierenden Container ins Auge fasst. Die kompromittierten Images brachten es zusammen auf über 120.000 Pull-Anfragen (in Worten: ein hundert zwanzig tausend).

Was Hacker mit Docker so alles anstellen können, spottet jeder Beschreibung:

  • auf das Dateisystem des Docker-Hosts und gemountete Volumes zugreifen
  • das interne Netzwerk scannen
  • Anmeldeinformationen absahnen und andere Daten exfiltrieren
  • ein Botnet einrichten, Container „anwerben“ und Angriffe gegen Dritte mit maskierten IPs abfeuern
  • „Dienste“ für Phishing-Kampagnen und Malware hosten
  • und dergleichen anderes.

In totaler Verzweiflung sind einige Nutzer auf Alternativen wie mitlxc/podman ausgewichen. Doch so weit muss man gar nicht gehen. Docker lässt sich durchaus absichern – wenn man weiß, wie.

1. Den Socket des Docker-Daemons niemals offenlegen

Ein Daemon-Socket sollte niemals für Remote-Verbindungen verfügbar sein, außer beim Einsatz eines authentifizierungsfähigen HTTPS-Sockets.

Der Docker-Daemon-Socket ist ein Unix-Netzwerksocket für die Kommunikation mit der Docker-API. Der Eigentümer des Sockets ist standardmäßig der Superuser root. Greift ein anderer Linux-Nutzer auf den Socket zu, verschafft er sich die gleichen Berechtigungen wie der Superuser auf dem Host.

Es ist im Übrigen möglich, den Socket des Daemons an eine Netzwerkschnittstelle zu binden, um den Docker-Container für Fernzugriffe zugänglich zu machen. Diese viel zu offenherzige Option kann vor allem bei Containern in der Produktionsumgebung leicht nach hinten los gehen.

Docker-Images sollte man niemals mit einer Option wie -v initialisieren, also eben nicht mittels:

/var/run/docker.sock://var/run/docker.sock

Diese Option würde den Socket im resultierenden Container offenlegen. Cyber-Täter lassen sich bei so einem kardinalen Fehler nicht erst zwei Mal bitten.

2. Auf privilegierte Container verzichten

Privilegierte Container – also jene, die mit root-Rechten initialisiert wurden – zählen zu den größten Quellen allen Übels. Sie erlauben nämlich den Root-Zugriff auf alle Systemgeräte (devices) von einem Container heraus, können Linux-Sicherheitsmodule wie AppArmor und SELinux manipulieren. Sie können sogar den Kernel des Host-Systems dazu verleiten, eine neue Instanz der Docker-Plattform einzurichten, um Docker innerhalb von Docker auszuführen und sich so der Überwachung durch Sicherheitstools auf der Systemebene entziehen.

Ein Angreifer kann von einem solchen kompromittierten Container heraus seine Privilegien „hochschrauben“ und sich den root-Zugriff auf ganze Knoten und Cluster verschaffen.

Um zu überprüfen, ob ein Container im privilegierten Modus läuft, hilft der Befehl:

docker inspect --format =''[container_id]

Der Befehl gibt true zurück, wenn der Container privilegiert ist, oder eine Fehlermeldung aus. Wer keine Fehlermeldung bekommt, muss also die Ärmel hochkrempeln und die Konfiguration abdichten, sodass Docker im rootless-Modus läuft.

3. Docker im Rootless-Modus ausführen

Mit dem rootless-Modus bietet Docker eine Möglichkeit, Docker-Daemons und -Container als Nicht-Root-Benutzer auszuführen, um Schwachstellen der Container-Laufzeit und der Daemons zu entschärfen.

Im rootless-Modus laufen nämlich Docker-Daemons und -Container im Benutzernamensraum, ohne root-Rechte. Das steht im Kontrast zum userns-remap-Modus, bei dem der Daemon leichtsinnigerweise mit Root-Rechten läuft.

Um Container im rootless-Modus auszuführen, muss man Docker verwirrenderweise dennoch mit Root-Rechten des Host-Systems installieren. Es werden außerdem zusätzliche Module benötigt, und zwar unter anderem newuidmap und newgidmap aus dem Package uidmap sowie das Package dbus-user-session. Dann gelingt nämlich die Initialisierung des Daemons mittels:

systemctl --user start docker

und entsprechend:

systemctl --user enable docker

Das Ausführen des Docker-Daemons als systemweiten Dienstes im rootless-Modus wird nicht unterstützt.

Für den Docker-Client ist es erforderlich, entweder den Pfad zum Socket oder den CLI-Kontext zu spezifizieren, konkret also entweder:

export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock

oder:

docker context use rootless

4. Grenzen setzen: Ressourcenkontingente festlegen

Um das Schlimmste zu verhindern, empfiehlt es sich, die Zuweisung von Ressourcen der CPU und des Arbeitsspeichers zu Containern zu limitieren. Die Standardkonfiguration ist in diesem Punkt relativ leichtsinnig. Denn bereits ein einziger Container kann das RAM und die CPU des Hosts voll bis an den Anschlag auslasten.

Um die maximale Speichernutzung eines Containers zu begrenzen, fügen Sie dem Befehl

docker run

die Option --memory hinzu, zum Beispiel:

sudo docker run -it --memory="1g" dockerimage

Damit der Container nicht aus dem Speicher läuft, kommt der Parameter --memory-swap zum Einsatz:

sudo docker run -it --memory="1g" --memory-swap="2g" dockerimage

Beim Erreichen der Speicherbegrenzung schreibt der Container jetzt auf den lokalen Datenspeicher statt in den Arbeitsspeicher. Das führt zu einem Performance-Verlust der betreffenden Anwendung, beeinträchtigt jedoch nicht die Leistungsfähigkeit anderer Prozesse auf dem Host (es sei denn, sie müssen auch kräftig swappen, was ja natürlich nicht sein sollte; Swappen ist nicht der Sinn der Übung, sondern ein Workaround).

Der Parameter --cpu-shares beschränkt die zulässige „Teilhabe“ des Containers an den CPU-Zyklen des Hosts. Mit der Option --cpus lässt sich der Container auf die gewünschte Anzahl von CPU-Kernen beschränken.

Ressourcenkontingente helfen, die Auswirkungen einer Sicherheitsverletzung zu minimieren, und sei es nur, damit sich für die Angreifer das Crypto-Mining nicht rechnet, da sie andere Arbeitslasten nicht mehr verdrängen können.

5. Images nach Verwundbarkeiten und Secrets durchscannen

Docker-Images können sensible Zugangsinformationen, die sogenannten Secrets, beinhalten, und Verwundbarkeiten einschleusen.

Beim Scannen von zweitausend öffentlichen Images hat GitGuardian in sieben Prozent mindestens ein Secret ausfindig machen können. Diese Geheimnisse fanden ihren Weg in Container-Images durch ungeprüften Quellcode und gestapelte Images mit Supply-Chain-Lecks (Verwundbarkeiten aus Abhängigkeiten).

Container erben unter anderem auch die Fehlkonfigurationen, Malware und andere Probleme. Es empfiehlt sich, nur signierte Images zu verwenden und selbst diese nach Bedrohungen durchzuscannen, bevor sie mit der Initialisierung von Containern in die Produktionspipeline hineinfließen (Stichwort: shift left). Das minimiert die Angriffsfläche.

Einige der beliebtesten Verwundbarkeitsscanner sind quelloffen, darunter Anchore Engine, CoreOS/Clair, Vuls.io und OpenSCAP.

6. Container auf dem Host-System isolieren

Das Host-Betriebssystem sollte Container von Hacking-Eskapaden schützen und eine gegenseitige Beeinflussung der Prozesse verhindern. Linux-Features wie Namensräume (die Grundlage der Container-Isolation), SELinux (eine zusätzliche Sicherheitsebene in der Red Hat-Familie) oder AppArmor (eine Kernel-Erweiterung in Debian-Derivaten), Cgroups (ein Kontrollmechanismus der Ressourcennutzung), Linux-Capabilities (prozessspezifische Berechtigungen) und seccomp (Secure Computing Mode des Linux-Kernels kann Systemaufrufe unterbinden) zählen zu den bewährtesten Tools des Host-Betriebssystems. Sie können Docker-Container auf einem Host in ihre Schranken weisen.

7. Container im Netzwerk abschotten

Damit Docker-Container über die Netzwerkschnittstellen des Hosts mit der Außenwelt kommunizieren können, muss eine Netzwerkschicht vermitteln. Alle Docker-Hosts verfügen hierzu über die unsichere Standard-Bridge. Wer für neue Container nicht explizit ein anderes Netzwerk vorschreibt, verbinden sich diese automatisch eben mit der Standard-Bridge. Das ist suboptimal, um nicht zu sagen leichtsinnig.

Container sollten nur dann eine Verbindung zueinander herstellen können, wenn sich das auf Grund ihrer Aufgabe nicht vermeiden lässt. Sicherheitskritische Container sind ohne direkten Zugang zu öffentlichen Netzwerken besser aufgehoben, als wenn sie mit dem Internet „plaudern“ können.

Docker-Nutzern sei empfohlen, eigene Bridge-Netzwerke einzurichten. Die ist erforderlich, um die Kommunikation der Container untereinander in den Griff zu bekommen, und um die automatische DNS-Auflösung von Containernamen zu IP-Adressen zu aktivieren. Der Nutzer kann beliebig viele Netzwerke anlegen und konkret spezifizieren, mit welchen Netzwerken sich jeder Container verbinden darf (falls überhaupt).

Für unterschiedliche Nutzungsszenarien hat Docker unterschiedliche eigene Netzwerktreiber, darunter: Bridge, Overlay, ipvlan und Macvlan.

Implementierungsbeispiel für den Netzwerkmodus IPvlan 802.1q trunk L2 zur Umsetzung von abgesicherten Kreditkartentransaktionen in Docker.
Implementierungsbeispiel für den Netzwerkmodus IPvlan 802.1q trunk L2 zur Umsetzung von abgesicherten Kreditkartentransaktionen in Docker.
(Bild: Docker)

Eine Bridge verbindet Standalone-Container. Der Overlay-Netzwerktreiber erstellt ein verteiltes Netzwerk zwischen mehreren Docker-Hosts, oberhalb der hostspezifischen Netzwerke, zur Bereitstellung von Swarm-Diensten. Ein ipvlan-Netzwerk gibt dem Nutzer die ultimative Kontrolle über IPv4- und IPv6-Adressen. Der Macvlan-Treiber ruft ein per-Host-konfiguriertes Netzwerk mit nur lokaler Reichweite ins Leben.

Zusätzliche Netzwerk-Plugins zur Integration von Docker mit spezialisierten Netzwerk-Stacks sind auch von Drittanbietern verfügbar. Der Nutzer kann im Übrigen auch eigene Plug-Ins erstellen.

8. Containern Schreibzugriffe entziehen

Ein einfacher und wirksamer Sicherheitstrick besteht darin, Container mit einem schreibgeschützten Dateisystem zu initialisieren, zum Beispiel mit Docker CLI:

docker run --read-only [image-name]

Nach dem Neustart des Dienstes ist das Dateisystem des Containers nur für Lesezugriffe zugänglich.

Einige Anwendungen kommen jedoch ohne Schreibzugriffe nicht aus. Um Fehlfunktionen zu verhindern, kann man Docker mit dem Parameter tmpfs einen isolierten Speicherort für Schreibzugriffe bereitstellen, zum Beispiel:

docker run --read-only --tmpfs /run/nginx --tmpfs /run/lock [image]

Dieser Ansatz kann böswillige Aktivitäten wie das Einschleusen von Malware in den Container oder das Abändern der Konfiguration verhindern, ohne dass sich die betroffene Anwendung „beschweren“ kann.

Im Gegensatz zu Volumes und Bind-Mounts lassen sich tmpfs-Mounts nicht für andere Container freigeben.

9. Aktualisieren, aktualisieren, aktualisieren...

Doch alles bringt nichts, wenn die Software mit den verfügbaren Aktualisierungen für Zero-Day-Exploits nicht Schritt hält. Das gilt sowohl für die Docker-Engine als auch das zugrundeliegende Host-Betriebssystem.

Fazit

Docker-Container „von der Stange“ sind eine Einladung zu Raubüberfällen. Zum Glück geht es auch anders: mit bewährten Best-Practices zum sicheren „Ein-Docken“ von Arbeitslasten.

Dieser Beitrag stammt von unserem Partnerportal Security-Insider.de.

Über die Autoren: Anna Kobylinska und Filipe Pereira Martins arbeiten für McKinley Denali Inc. (USA).

Jetzt Newsletter abonnieren

Verpassen Sie nicht unsere besten Inhalte

Mit Klick auf „Newsletter abonnieren“ erkläre ich mich mit der Verarbeitung und Nutzung meiner Daten gemäß Einwilligungserklärung (bitte aufklappen für Details) einverstanden und akzeptiere die Nutzungsbedingungen. Weitere Informationen finde ich in unserer Datenschutzerklärung.

Aufklappen für Details zu Ihrer Einwilligung

(ID:47973956)