Stefan Esser, Co-Autor von "The iOS Hacker's Handbook" berichtet von einem Bug in OS X 10.10 Yosemite, der normalen Benutzern Root-Rechte ermöglichen kann. Das Problem ist eine neue nicht korrekt abgesicherte Umgebungsvariable namens DYLD_PRINT_TO_FILE, die von dyld, dem dynamischen Linker des Systems, benutzt wird.
Der Bug wurde mit dem Update auf 10.10.5 von Apple inzwischen behoben.
Ein dynamische Linker sorgt dafür, daß benötigte externe Programm-Bibliotheken einem Programm zur Laufzeit zur Verfügung stehen. Mac OS X verwendet seit seiner ersten Version 10.0 den dyld für diese Aufgabe.
Bereits in meinem Analyse-Artikel über den Flashback-Trojaner, den ich für heise geschrieben hatte, erwähnte ich den dyld.
Umso mehr wundert es mich, daß heise in seinem Artikel über die von Stefan dokumentierte Schwachstelle schreibt, dyld wäre mit 10.10 eingeführt worden. Auch auf apfeltalk mußte ich das bemängeln. Offenbar haben die Redakteure weder die Originalquelle gelesen noch eine Ahnung, wie OS X seit Ewigkeiten schon funktioniert.
Mit 10.10 Yosemite wurde dem dyld unter anderem zusätzlich die neue Umgebungsvariable DYLD_PRINT_TO_FILE spendiert. Diese erlaubt dem Linker, seine Fehlerausgaben in eine beliebige Datei zu schreiben. Standardmäßig schreibt er Fehler auf stderr, dem Standard-Error-Stream, raus.
Solche Tatsachen halten jedoch auch bekannte Security-Seiten wie "packet storm" nicht davon ab, Unfug zu schreiben. Packet Storm erzählt, die Verwundbarkeit würde 10.10.4 und früher betreffen. Auch dort werden die Artikel anscheinend von Katzen geschrieben, die über die Tastatur huschen. Jedenfalls haben sie ihre sagenhaften drei Zeilen nicht ohne diesen fachlichen Fehler hinbekommen. Möglicherweise haben sie es bei Metasploit abgeschrieben. Die problematische Variable wurde jedenfalls erst mit 10.10.0 eingeführt.
Normalerweise wendet der Linker Umgebungsvariablen nicht auf sicherheitskritische Programme an, damit deren Verhalten nicht manipuliert werden kann durch Unbefugte. Sicherheitskritisch sind zum Beispiel SUID-Programme, also solche, die unabhängig davon, wer sie startet, unter dem Root-User laufen, um ihre Aufgabe erledigen zu können. Ruft man ein solches Programm mit einer Umgebungsvariablen auf, wird diese ignoriert.
In dem Fall der neuen Umgebungsvariable DYLD_PRINT_TO_FILE jedoch findet die Prüfung, ob ein sicherheitsrelevantes Programm aufgerufen wurde, laut Stefan nicht statt, weil sie nicht wie die anderen Variablen in der processDyldEnvironmentVariable() Funktion behandelt wird, sondern direkt in der Hauptfunktion.
Ich bin mit Stefans Darstellung in diesem Punkt nicht ganz glücklich, weil es bei ihm so klingt, als würde processDyldEnvironmentVariable() selbst die Bereinigung vornehmen. Das Säubern passiert allerdings in pruneEnvironmentVariables(), nachdem checkLoadCommandEnvironmentVariables() processDyldEnvironmentVariable() bereits aufgerufen hatte. Zu sehen in Apples Open Source zu dyld.
Anekdote 1
Der dyld ist übrigens in C++ geschrieben. Der ganze Kernel ist C und C++. Die GUI-Libs sind C und Objective-C. Noch ein Grund, warum Apple nicht auf die C-Sprachen verzichten kann mit Swift.
Und der Fehler würde in jeder Sprache passieren, weil es ein logischer Fehler ist, wann was geprüft werden muß, und keine Eigenschaft der Programmiersprache.
Anekdote 2
Manche Entwickler, die diese Sicherheits-Funktion von dyld nicht kennen, halten das sogar für einen Bug. Zumindest sind sie genervt davon, daß das Ignorieren von Umgebungsvariablen auf der Shell zurückgemeldet wurde, wenn sicherheitsrelevante (SUID)-Binaries vor Umgebungsvariablen des dyld geschützt wurden.
Im dyld-Quellcode steht dann auch als Kommentar: "Disable warnings about DYLD_ env vars being ignored. The warnings are causing too much confusion."
In der processDyldEnvironmentVariable Funktion steht sogar dieser Kommentar für DYLD_PRINT_TO_FILE:
// handled in _main()
Es ist also kein Versehen. In 10.11 wäre das Problem laut Stefan allerdings behoben worden, indem DYLD_PRINT_TO_FILE wie alle anderen Umgebungsvariablen in processDyldEnvironmentVariable behandelt wird.
In einem Tweet bringt Stefan dieses Beispiel:
echo 'echo "$(whoami) ALL=(ALL) NOPASSWD:ALL" >&3' | DYLD_PRINT_TO_FILE=/etc/sudoers newgrp; sudo -s
Diese Zeile bedarf etwas Erklärung. Sie besteht aus zwei Befehlen, die nacheinander ausgeführt werden. Erst der links vom Semikolon, dann der rechts davon. Der linke Befehle besteht seinerseits aus vier kombinierten Kommandos: newgrp, whoami und zweimal echo.
Das innere echo erzeugt also eine Zeile, die in die suoders-Datei geschrieben werden soll. Das äußere echo schreibt die Zeile des inneren echo in den File-Descriptor 3. Dieser File-Descriptor 3 ist die offene /etc/sudoers Datei, weil die durch DYLD_PRINT_TO_FILE für newgrp geöffnet wurde. Und newgrp konnte die öffnen, weil es unter root startet.
Danach wird mit sudo -s dem aktuellen User eine Root-Shell geöffnet. Das funktioniert, weil es jetzt so erlaubt ist laut der geänderten sudoers-Datei.
In dem Zusammenhang kann ich auf diese Artikel verweisen: Bash One-Liners Explained, Part III: All about redirections, um mehr über File-Descriptoren und Umleitungen zu lernen. Und Tweet sized Mac OS X 10.10 exploit sowie How does the DYLD privilege escalation vulnerability work on OS X?.
Das eine Problem ist, daß der Linker die DYLD_PRINT_TO_FILE an newgrp weitergibt und damit ein Root-Prozeß kompromittiert werden kann durch den Inhalt dieser Umgebungsvariablen. Und für alle anderen SUID-Binaries würde der Linker dasselbe tun. Der Linker dyld müßte die Umgebungsvariable für newgrp (und andere SUID-Binaries) droppen.
Das andere Problem ist, daß der File-Descriptor 3, den das SUID-Binary newgrp als Root öffnet, in der von newgrp erzeugten Sub-Shell, die unter dem normalen User läuft, weiter verwendet werden kann. So kann der User in eine Datei schreiben, die normalerweise nicht in seinem Zugriff steht. Hier wäre es besser, wenn der File-Desciptor, den Root geöffnet hat, nicht an Nicht-Root-Sub-Shells weitergegeben würde.
Ich würde mir wünschen, daß solche Änderungen an sensiblen Code-Stellen von kompetenten Leuten bei Apple kontrolliert werden, bevor sie auf die Menschheit losgelassen werden. Wer kommt nur auf die Idee, die eine Variable anders zu behandeln als alle anderen und dies sogar noch als Kommentar reinzuschreiben? War da ein Neuling ohne Gegenkontrolle am Werk?
Stefan bietet als Übergangslösung, bis Apple das auch in Yosemite und nicht nur in El Capitan behebt, einen Kernel-Treiber an, der das Problem lösen kann. Allerdings behebt dies nicht das eigentliche Problem und könnte durch einen gegnerischen Treiber wieder ausgehebelt werden.