Race Condition, die Link-Following ermöglicht
Beschreibung
Race Condition, die Link-Following ermöglicht, ist eine Time-of-Check Time-of-Use (TOCTOU)-Schwachstelle, die auftritt, wenn ein Programm die Eigenschaften einer Datei prüft (z.B. ob es ein symbolischer Link ist) und später eine Operation auf derselben Datei durchführt, aber ein Angreifer die Datei zwischen der Prüfung und der Verwendung in einen symbolischen Link ändern kann. Dies schafft ein Zeitfenster, in dem das Programm glaubt, es operiere auf einer sicheren, regulären Datei, aber tatsächlich auf einer anderen Datei durch einen bösartigen Symlink operiert. Die Schwachstelle ist besonders gefährlich, wenn das Programm mit erhöhten Privilegien (setuid/setgid) läuft, da Angreifer Dateioperationen auf sensible Systemdateien wie /etc/passwd, /etc/shadow oder Konfigurationsdateien umleiten können. Das fundamentale Problem ist, dass der während der Verifizierungsphase geprüfte Dateisystemzustand möglicherweise nicht derselbe ist wie der Zustand, wenn die tatsächliche Operation stattfindet, und diese Race Condition kann durch Techniken wie das Starten mehrerer Threads, die wiederholt Dateien austauschen, zuverlässig ausgenutzt werden.
Risiko
TOCTOU-Symlink-Race-Conditions ermöglichen lokale Privilegieneskalation, beliebiges Dateiüberschreiben und unautorisierten Dateizugriff. Diese Schwachstellen sind besonders kritisch in setuid-Binaries, Container-Runtimes und Systemdiensten, die Dateien im Auftrag von Benutzern handhaben. Docker erlitt mehrere kritische TOCTOU-Schwachstellen: CVE-2018-15664 ermöglichte Container-Escape durch Symlink-Racing während docker-cp-Operationen, und CVE-2024-23651 in BuildKit ermöglichte Angreifern, beliebige Host-Dateien während Image-Builds zu löschen oder zu überschreiben. Das Tesla Model 3 wurde bei Pwn2Own 2023 durch eine TOCTOU-Race-Condition (CVE-2023-3282) in ConnMan kompromittiert, was Codeausführung auf Root-Ebene ermöglichte. Windows Defender war anfällig für CVE-2019-1161, eine TOCTOU-Race, die Angreifern ermöglichte, beliebige Dateien als SYSTEM zu löschen. Die Ausnutzungstechnik ist gut verstanden und zuverlässig: Angreifer verwenden hochfrequentes Symlink-Wechseln kombiniert mit Timing-Manipulation, um das Race konsistent zu gewinnen. Selbst bei kleinen Zeitfenstern machen moderne Mehrkern-Systeme die Ausnutzung durch parallele Versuche praktikabel.
Lösung
Verwenden Sie atomare Dateisystemoperationen, die nicht durch Symlink-Änderungen unterbrochen werden können. Öffnen Sie Dateien mit dem O_NOFOLLOW-Flag, um bei einem symbolischen Link als Ziel zu scheitern, anstatt ihm zu folgen. Verwenden Sie dateideskriptorbasierte Operationen (fstat(), fchown(), fchmod()) nach dem Öffnen der Datei statt pfadbasierter Operationen (stat(), chown(), chmod()), die geraced werden können. Für setuid-Programme legen Sie Privilegien vorübergehend mit seteuid(getuid()) ab, bevor Sie Dateioperationen in benutzerkontrollierten Verzeichnissen durchführen, dann stellen Sie Privilegien nur bei Bedarf wieder her. Beim Erstellen von Dateien in Verzeichnissen, die für Angreifer beschreibbar sein können (wie /tmp), verwenden Sie mkstemp() oder ähnliche Funktionen, die atomar Dateien erstellen und öffnen. Implementieren Sie ordnungsgemäße Verzeichnisisolation: Erstellen Sie Dateien in Verzeichnissen, in die nur der privilegierte Prozess schreiben kann. Für Container-Runtimes verwenden Sie Mount-Namespaces und verhindern Sie Symlink-Auflösung über Mount-Grenzen hinweg. Erwägen Sie die Verwendung von Linux-Capabilities (CAP_FOWNER, etc.) anstelle von vollen setuid-Privilegien. Implementieren Sie Verteidigung in der Tiefe durch mehrfache Validierung von Dateipfaden und Verwendung von chroot oder pivot_root, um Dateisystemzugriff einzuschränken.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Integrität | Umfang: Integrität Dateien oder Verzeichnisse modifizieren - Angreifer können Schreiboperationen umleiten, um beliebige Dateien zu überschreiben, indem sie das Ziel durch einen Symlink zu sensiblen Systemdateien wie /etc/passwd oder Anwendungskonfigurationsdateien ersetzen. |
| Vertraulichkeit | Umfang: Vertraulichkeit Dateien oder Verzeichnisse lesen - Leseoperationen können durch Symlinks umgeleitet werden, um auf Dateien zuzugreifen, die der Angreifer normalerweise nicht lesen kann, wodurch sensible Daten wie private Schlüssel, Passwort-Hashes oder vertrauliche Konfigurationen exfiltriert werden. |
| Zugriffskontrolle | Umfang: Zugriffskontrolle Privilegien erlangen oder Identität annehmen - Wenn in setuid/setgid-Programmen oder privilegierten Diensten ausgenutzt, können Angreifer zu Root- oder System-Level-Zugang eskalieren, indem sie sicherheitskritische Dateien manipulieren. |
Beispielcode
Anfälliger Code
// ANFÄLLIG: Klassische TOCTOU Race Condition in setuid-Programm
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
void vulnerable_file_operation(const char *filename) {
struct stat st;
// ANFÄLLIG: Time-of-Check - verifizieren dass Datei kein Symlink ist
if (lstat(filename, &st) < 0) {
perror("lstat");
return;
}
// ANFÄLLIG: Prüfen ob es ein Symlink ist
if (S_ISLNK(st.st_mode)) {
fprintf(stderr, "Fehler: %s ist ein symbolischer Link\n", filename);
return;
}
// ANFÄLLIG: Besitz prüfen
if (st.st_uid != getuid()) {
fprintf(stderr, "Fehler: Sie besitzen %s nicht\n", filename);
return;
}
// RACE-FENSTER: Angreifer ersetzt Datei hier durch Symlink!
// Zwischen lstat() und open() kann Angreifer:
// rm /tmp/benutzerdatei
// ln -s /etc/passwd /tmp/benutzerdatei
// ANFÄLLIG: Time-of-Use - Datei öffnen und beschreiben
// Operiert jetzt auf /etc/passwd mit Root-Privilegien!
FILE *fp = fopen(filename, "w"); // Öffnet Symlink-Ziel
if (fp) {
fprintf(fp, "Benutzerdaten geschrieben von privilegiertem Programm\n");
fclose(fp);
}
}
// ANFÄLLIG: Setuid-Root-Programm
int main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(stderr, "Verwendung: %s <dateiname>\n", argv[0]);
return 1;
}
// Läuft als Root (setuid), aber vertraut benutzerbereitgestelltem Pfad
vulnerable_file_operation(argv[1]);
return 0;
}
# ANFÄLLIG: Python TOCTOU Race in privilegiertem Skript
import os
import stat
def vulnerable_delete_user_file(filepath):
# ANFÄLLIG: Prüfen ob Datei ein Symlink ist
if os.path.islink(filepath):
raise ValueError("Symlinks nicht erlaubt")
# ANFÄLLIG: Prüfen ob Datei dem anfragenden Benutzer gehört
file_stat = os.lstat(filepath)
if file_stat.st_uid != os.getuid():
raise PermissionError("Diese Datei gehört Ihnen nicht")
# RACE-FENSTER: Angreifer kann Datei hier durch Symlink ersetzen
# Skript läuft als Root und löscht das Symlink-Ziel
# ANFÄLLIG: Time-of-Use mit Root-Privilegien
os.remove(filepath) # Könnte /etc/passwd löschen wenn geraced
def vulnerable_temp_file_creation():
# ANFÄLLIG: Vorhersagbarer temporärer Dateipfad
temp_path = f"/tmp/app_temp_{os.getpid()}"
# ANFÄLLIG: Existenz prüfen dann erstellen
if os.path.exists(temp_path):
os.remove(temp_path)
# RACE-FENSTER: Angreifer erstellt Symlink bei temp_path
# ANFÄLLIG: Erstellt/überschreibt durch Symlink
with open(temp_path, 'w') as f:
f.write("sensible daten")
Korrigierter Code
// KORRIGIERT: Sichere Dateioperationen mit Dateideskriptoren und O_NOFOLLOW
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
int safe_file_operation(const char *filename) {
struct stat st;
int fd;
uid_t real_uid = getuid();
uid_t effective_uid = geteuid();
// KORRIGIERT: Privilegien ablegen bevor auf Benutzerdateien zugegriffen wird
if (seteuid(real_uid) < 0) {
perror("seteuid");
return -1;
}
// KORRIGIERT: Mit O_NOFOLLOW öffnen - scheitert wenn Symlink
// Dies ist atomar - kein Race-Fenster
fd = open(filename, O_WRONLY | O_NOFOLLOW);
if (fd < 0) {
if (errno == ELOOP) {
fprintf(stderr, "Fehler: %s ist ein symbolischer Link\n", filename);
} else {
perror("open");
}
seteuid(effective_uid); // Wiederherstellen für Aufräumen
return -1;
}
// KORRIGIERT: Dateideskriptor-Operationen verwenden, nicht Pfad-Operationen
// Diese operieren auf der geöffneten Datei, immun gegen Symlink-Ersetzung
if (fstat(fd, &st) < 0) {
perror("fstat");
close(fd);
seteuid(effective_uid);
return -1;
}
// KORRIGIERT: Besitz mit Dateideskriptor-stat verifizieren
if (st.st_uid != real_uid) {
fprintf(stderr, "Fehler: Diese Datei gehört Ihnen nicht\n");
close(fd);
seteuid(effective_uid);
return -1;
}
// KORRIGIERT: Mit Dateideskriptor schreiben, nicht Pfad
const char *data = "Benutzerdaten sicher geschrieben\n";
if (write(fd, data, strlen(data)) < 0) {
perror("write");
close(fd);
seteuid(effective_uid);
return -1;
}
close(fd);
// Privilegien nur wiederherstellen wenn tatsächlich benötigt
if (seteuid(effective_uid) < 0) {
perror("seteuid wiederherstellen");
return -1;
}
return 0;
}
// KORRIGIERT: Sichere temporäre Dateierstellung
int safe_temp_file(char **out_path) {
// KORRIGIERT: mkstemp für atomares Erstellen+Öffnen verwenden
char template[] = "/tmp/app_XXXXXX";
int fd = mkstemp(template);
if (fd < 0) {
perror("mkstemp");
return -1;
}
// KORRIGIERT: Restriktive Berechtigungen sofort setzen
if (fchmod(fd, 0600) < 0) {
perror("fchmod");
close(fd);
unlink(template);
return -1;
}
*out_path = strdup(template);
return fd;
}
# KORRIGIERT: Python sichere Dateioperationen
import os
import stat
import tempfile
def safe_delete_user_file(filepath, requesting_uid):
"""Datei sicher löschen, immun gegen TOCTOU-Symlink-Angriffe."""
# KORRIGIERT: Mit O_NOFOLLOW-Äquivalent öffnen - Symlinks nicht folgen
try:
# os.open mit O_NOFOLLOW verwenden um Symlinks atomar zu erkennen
fd = os.open(filepath, os.O_RDONLY | os.O_NOFOLLOW)
except OSError as e:
if e.errno == errno.ELOOP:
raise ValueError("Symlinks nicht erlaubt")
raise
try:
# KORRIGIERT: fstat auf Dateideskriptor verwenden - immun gegen Race
file_stat = os.fstat(fd)
# KORRIGIERT: Besitz mit fstat-Ergebnis prüfen
if file_stat.st_uid != requesting_uid:
raise PermissionError("Diese Datei gehört Ihnen nicht")
# KORRIGIERT: Verzeichnis-fd für unlinkat holen
dir_path = os.path.dirname(os.path.abspath(filepath))
filename = os.path.basename(filepath)
dir_fd = os.open(dir_path, os.O_RDONLY | os.O_DIRECTORY)
try:
# KORRIGIERT: unlinkat mit dir_fd für atomare Operation verwenden
os.unlink(filename, dir_fd=dir_fd)
finally:
os.close(dir_fd)
finally:
os.close(fd)
def safe_temp_file_creation():
"""Temp-Datei atomar erstellen, immun gegen Symlink-Angriffe."""
# KORRIGIERT: tempfile-Modul verwenden, das Atomarität handhabt
fd, path = tempfile.mkstemp(prefix='app_', suffix='.tmp')
try:
# KORRIGIERT: Berechtigungen mit Dateideskriptor setzen
os.fchmod(fd, 0o600)
# KORRIGIERT: Mit Dateideskriptor schreiben
os.write(fd, b"sensible daten")
return path
except:
os.close(fd)
os.unlink(path)
raise
finally:
os.close(fd)
Der anfällige Code demonstriert klassische TOCTOU-Muster, bei denen Dateieigenschaften mit pfadbasierten Operationen geprüft werden, was ein Race-Fenster vor der tatsächlichen Dateioperation schafft. Der korrigierte Code eliminiert das Race durch Verwendung von O_NOFOLLOW, um Symlinks atomar abzulehnen, dateideskriptorbasierte Operationen, die auf der bereits geöffneten Datei operieren, Privilegienablegung vor dem Zugriff auf benutzerkontrollierte Pfade und atomare Dateierstellung mit mkstemp().
Ausgenutzt in der Praxis
Docker BuildKit TOCTOU-Schwachstelle (Docker/Global, 2024)
CVE-2024-23651 betraf Docker BuildKit-Versionen vor 0.12.5 und ermöglichte Angreifern, eine TOCTOU-Race-Condition während Cache-Mount-Operationen auszunutzen. Durch schnelles Modifizieren von Symlinks in einem Cache-Mount könnten Angreifer BuildKit dazu bringen, einen Symlink zu einem beliebigen Host-Pfad aufzulösen, was ihnen ermöglichte, Dateien außerhalb des Containers während Image-Builds zu lesen, zu schreiben oder zu löschen. Die Schwachstelle könnte durch das Erstellen eines bösartigen Dockerfiles oder die Verwendung kompromittierter Build-Kontexte ausgelöst werden. Dieser Angriff demonstrierte, wie Container-Isolation durch Dateisystem-Race-Conditions vollständig umgangen werden kann, was potenziell ganze CI/CD-Pipelines und Build-Infrastruktur kompromittiert.
- https://www.docker.com/blog/docker-security-advisory-multiple-vulnerabilities-in-runc-buildkit-and-moby/
- https://nvd.nist.gov/vuln/detail/CVE-2024-23651
Tesla Model 3 ConnMan TOCTOU-Exploit (Tesla/Pwn2Own 2023)
Bei Pwn2Own Automotive 2023 nutzten Sicherheitsforscher von Synacktiv eine TOCTOU-Race-Condition (CVE-2023-3282) in ConnMan aus, dem Netzwerkverbindungsmanager, der in Tesla-Fahrzeugen verwendet wird. Die Schwachstelle existierte darin, wie ConnMan DHCP-Optionen und temporäre Dateierstellung handhabte. Durch Gewinnen einer Race Condition während der Netzwerkkonfiguration könnten Angreifer beliebige Inhalte mit Root-Privilegien in beliebige Dateien schreiben. Kombiniert mit anderen Schwachstellen führte dies zur vollständigen Kompromittierung des Tesla-Infotainment-Systems mit Codeausführung auf Root-Ebene. Der Angriff erforderte nur, dass das Fahrzeug sich mit einem bösartigen WiFi-Netzwerk verbindet, was die Ausnutzbarkeit von Symlink-Race-Conditions in der realen Welt demonstriert.
- https://www.zerodayinitiative.com/blog/2023/4/27/pwn2own-vancouver-2023-day-two-results
- https://www.synacktiv.com/publications
Tools zum Testen/Ausnutzen
-
inotify-tools — Linux-Utilities zur Überwachung von Dateisystemereignissen, nützlich zum Verstehen von Race-Timing und Erstellen von Exploit-Bedingungen.
-
racing-pulse — Google Project Zeros Forschungstool zum Ausnutzen von Dateisystem-Race-Conditions, besonders nützlich zum Testen von TOCTOU-Schwachstellen.
-
symlink-racer — JFrogs Tool zum Testen von Symlink-TOCTOU-Schwachstellen durch schnelles Symlink-Wechseln.
CVE-Beispiele
-
CVE-2024-23651 — Docker BuildKit TOCTOU-Race-Condition, die Container-Escape durch Symlink-Manipulation während Cache-Mounts ermöglicht.
-
CVE-2023-3282 — ConnMan TOCTOU-Schwachstelle, die bei Pwn2Own ausgenutzt wurde, um Root auf Tesla Model 3 zu erlangen.
-
CVE-2019-1161 — Windows Defender TOCTOU-Race, die beliebiges Dateilöschen als SYSTEM ermöglicht.
-
CVE-2018-15664 — Docker-cp TOCTOU-Schwachstelle, die Container-Escape durch Symlink-Racing ermöglicht.
-
CVE-2008-0525 — PatchLink Update Client TOCTOU-Race-Condition, die lokale Privilegieneskalation ermöglicht.
Referenzen
-
MITRE Corporation. "CWE-363: Race Condition Enabling Link Following." https://cwe.mitre.org/data/definitions/363.html
-
CERT/CC. "TOC TOU - Time of Check, Time of Use Race Conditions." https://wiki.sei.cmu.edu/confluence/display/c/FIO45-C.+Avoid+TOCTOU+race+conditions+while+accessing+files
-
Docker. "Docker Security Advisory: Multiple Vulnerabilities in BuildKit." Februar 2024. https://www.docker.com/blog/docker-security-advisory-multiple-vulnerabilities-in-runc-buildkit-and-moby/
-
Wei, J. und Pu, C. "TOCTTOU Vulnerabilities in UNIX-Style File Systems: An Anatomical Study." USENIX Security 2005. https://www.usenix.org/legacy/event/fast05/tech/full_papers/wei/wei.pdf