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

AuswirkungDetails
IntegritätUmfang: 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.
VertraulichkeitUmfang: 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.
ZugriffskontrolleUmfang: 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.

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.


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

  1. MITRE Corporation. "CWE-363: Race Condition Enabling Link Following." https://cwe.mitre.org/data/definitions/363.html

  2. 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

  3. 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/

  4. 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