
Runtime-libc-Funktionsauditor, der Dateizugriffs-Race-Conditions und Symlink-Schwachstellen erkennt, indem er Dateisystem-Syscalls per LD_PRELOAD einhakt und potenzielle Privilegieneskalationspfade protokolliert.
Der PathAuditor ist ein Tool zur Erkennung von Schwachstellen im Zusammenhang mit Dateizugriffen, indem libc-Funktionen überprüft werden.
Die Idee ist grob wie folgt:
Wir verwenden LD_PRELOAD, um alle dateisystembezogenen Bibliotheksaufrufe zu hooken und etwaige Verstöße an syslog zu melden.
Dies ist kein offiziell unterstütztes Google-Produkt.
Betrachten wir ein Beispiel der Art von Schwachstelle, die dieses Tool erkennen kann. CVE-2019-3461 war ein Bug in tmpreaper, einem Tool, das /tmp durchläuft und alte Dateien löscht. Es wird normalerweise als Cron-Job mit root-Rechten ausgeführt. Da es keine Dateien außerhalb von tmp löschen möchte, wurde der folgende Code verwendet, um zu prüfen, ob ein Verzeichnis ein Mountpunkt ist:
if (S_ISDIR (sb.st_mode)) {
char *dst;
if ((dst = malloc(strlen(ent->d_name) + 3)) == NULL)
message (LOG_FATAL, "malloc failed.\n");
strcpy(dst, ent->d_name);
strcat(dst, "/X");
rename(ent->d_name, dst);
if (errno == EXDEV) {
free(dst);
message (LOG_VERBOSE,
"File on different device skipped: `%s/%s'\n",
dirname, ent->d_name);
continue;
}
// [...]
Kurz gesagt: Dieser Code ruft rename("/tmp/foo", "/tmp/foo/x") auf, was EXDEV zurückgibt, wenn "/tmp/foo" ein Mountpunkt ist.
PathAuditor würde diesen Aufruf als potenzielle Schwachstelle kennzeichnen, wenn "/tmp/foo" einem anderen Benutzer als root gehört.
Um zu verstehen, warum, müssen wir darüber nachdenken, was im Kernel passiert, wenn der rename-Syscall ausgeführt wird (vereinfacht):
"/tmp/foo" für das erste Argument."/tmp/foo/x" für das zweite Argument.Hier gibt es eine Race-Condition, da "/tmp/foo" zweimal aufgelöst wird. Wenn es benutzergesteuert ist, kann der Benutzer es jederzeit durch eine andere Datei ersetzen.
Insbesondere möchten wir, dass "/tmp/foo" zunächst ein Verzeichnis ist, um die if(S_ISDIR)-Prüfung im tmpreaper-Code zu bestehen. Wir ersetzen es dann kurz vor dem Eintritt in den Syscall durch eine Datei. Wenn der Kernel das erste Argument auflöst, sieht er eine Datei mit benutzergesteuertem Inhalt.
Jetzt ersetzen wir es erneut, diesmal durch einen Symlink auf ein beliebiges Verzeichnis im selben Dateisystem. Der Kernel löst den Pfad ein zweites Mal auf, folgt dem Symlink und verschiebt die kontrollierte Datei in ein ausgewähltes Verzeichnis.
Die Einschränkung auf dasselbe Dateisystem besteht, da rename nicht zwischen Dateisystemen funktioniert. Aber auf einigen Linux-Distributionen ist /tmp standardmäßig nur ein Ordner auf dem Root-Dateisystem, und man könnte diesen Bug nutzen, um eine Datei nach /etc/cron zu verschieben, die dann mit root-Rechten ausgeführt wird.
Zum Ausprobieren muss man libpath_auditor.so mit bazel bauen und es mittels LD_PRELOAD in ein Binary laden. Verstöße werden an syslog protokolliert, also stelle sicher, dass syslog läuft.
bazel build //pathauditor/libc:libpath_auditor.so
LD_PRELOAD=/path/to/bazel-bin/pathauditor/libc/libpath_auditor.so cat /tmp/foo/bar
tail /var/log/syslog
Es ist auch möglich, dies auf allen Prozessen des Systems auszuführen, indem man es in /etc/ld.so.preload einträgt. Allerdings sei gewarnt, dass dies nur auf Testsystemen empfohlen wird, da es zu Instabilität führen kann.
Als Schnellstart kannst du den Docker-Container ausprobieren, der mit diesem Projekt ausgeliefert wird:
docker build -t pathauditor-example .
docker run -it pathauditor-example
# LD_PRELOAD=/pathauditor/bazel-bin/pathauditor/libc/libpath_auditor.so cat /tmp/foo/bar
# cat /var/log/syslog