
PoC für CVE-2019-5736
PoC für CVE-2019-5736
Erstellt mit Hilfe von @singe, @_cablethief und @feexd
Getestet auf Ubuntu 18.04, Debian 9 und Arch Linux. Docker-Versionen 18.09.1-ce und 18.03.1-ce. Dieser PoC funktioniert derzeit nicht mit Ubuntu 16.04 und CentOS.
Schau dir den Exploit-Code von Dragon Sector (die Entdecker der Sicherheitslücke) hier an.
Dies ist eine Go-Implementierung von CVE-2019-5736, einem Container-Ausbruch für Docker. Der Exploit funktioniert, indem er das runc-Binärprogramm des Hostsystems von innerhalb des Containers überschreibt und ausführt.
Es gibt 2 Anwendungsfälle für den Exploit. Der erste (was dieses Repository ist) ist im Wesentlichen eine Falle. Ein Angreifer müsste innerhalb eines Containers die Ausführung von Befehlen erlangen und ein schädliches Binärprogramm starten, das lauscht. Wenn jemand (Angreifer oder Opfer) docker exec verwendet, um in den Container zu gelangen, wird der Exploit ausgelöst, der die Codeausführung als root ermöglicht.

Der zweite (was nicht dieses Repository ist) erstellt ein schädliches Docker-Image. Wenn dieses Image ausgeführt wird, wird der Exploit ausgelöst. Es ist kein exec in den Container erforderlich. Siehe unten in dieser Readme für ein GIF-Beispiel.
Um diese Sicherheitslücke auszunutzen, musst du root (uid 0) im Container sein.
Ja, du wirst deine Implementierung von runc überschreiben, was dazu führt, dass dein System keine Docker-Container mehr ausführen kann. Bitte sichere entweder /usr/bin/docker-runc oder /usr/bin/runc (je nachdem, was du hast; überprüfe auch /usr/sbin).
Ändere den Code nach Belieben und kompiliere ihn mit go build main.go. Verschiebe das Binärprogramm in den Container, aus dem du ausbrechen möchtest. Führe das Binärprogramm aus, und beim nächsten Mal, wenn sich jemand mit dem Container verbindet und /bin/sh aufruft, wird deine Nutzlast ausgelöst.
Dieser PoC wurde mit einer hervorragenden Erklärung aus diesem Commit im lxc-Projekt erstellt (zusammen mit hilfreichen Ratschlägen von anderen).
Als Beispiel: Wenn das Zielbinärprogramm /bin/bash wäre, könnte es durch ein ausführbares Skript ersetzt werden, das den Interpreterpfad #!/proc/self/exe angibt (/proc/self/exe ist ein symbolischer Link, der vom Kernel für jeden Prozess erstellt wird und auf das Binärprogramm zeigt, das für diesen Prozess ausgeführt wurde). Wenn also /bin/bash innerhalb des Containers ausgeführt wird, wird stattdessen das Ziel von /proc/self/exe ausgeführt – das auf das runc-Binärprogramm auf dem Host verweist.
Wir implementieren dies, indem wir /bin/sh im Container mit #!/proc/self/exe überschreiben, was auf das Binärprogramm zeigt, das diesen Prozess gestartet hat (den Docker exec).

Der Angreifer kann dann versuchen, auf das Ziel von /proc/self/exe zu schreiben, um das runc-Binärprogramm auf dem Host zu überschreiben. Im Allgemeinen wird dies jedoch nicht gelingen, da der Kernel es nicht erlaubt, es zu überschreiben, während runC ausgeführt wird. Um dies zu umgehen, kann der Angreifer stattdessen einen Dateideskriptor zu /proc/self/exe mit dem Flag O_PATH öffnen und dann das Binärprogramm über /proc/self/fd/ im Modus O_WRONLY erneut öffnen und in einer Schleife in einem separaten Prozess darauf zu schreiben versuchen.
Hinweis: Einige Teile des vorherigen Abschnitts sind nicht vollständig korrekt. Du musst das Flag O_PATH nicht verwenden, wenn du den Dateideskriptor für runcinit erhältst. Außerdem musst du die Schreibschleife nicht in einem anderen Prozess erstellen. Wir erhalten einen Dateideskriptor für den runcinit, indem wir einen Datei-Handle zu /proc/PID/exe erhalten. Von dort aus verwenden wir diesen Handle, um einen Datei-Handle zu /proc/self/fd/FILEDESCRIPTOR zu erhalten. Dies ist der Datei-Handle, den wir zum Schreiben verwenden.

Letztendlich wird es erfolgreich sein, wenn das runC-Binärprogramm beendet wird. Danach ist das runC-Binärprogramm kompromittiert und kann verwendet werden, um andere Container oder den Host selbst anzugreifen.
Wenn wir in der Lage sind, auf diesen Datei-Handle zu schreiben, haben wir das runc-Binärprogramm auf dem Host überschrieben. Wir können beliebige Befehle als root ausführen.
Dieses Repository enthält dieses Beispiel nicht, aber du findest es hier. Meiner Meinung nach ist dies das weitaus gefährlichere Szenario. Führe einfach das schädliche Container-Image aus und du erhältst Codeausführung als root. Eine detaillierte Erklärung findest du hier.
