Übertragung privater Ressourcen in eine neue Sphäre ('Ressourcenleck')

Beschreibung

Übertragung privater Ressourcen in eine neue Sphäre tritt auf, wenn ein Produkt Ressourcen für nicht vertrauenswürdige Parteien verfügbar macht, obwohl diese Ressourcen nur vom Produkt selbst zugegriffen werden sollten. Diese Schwäche beinhaltet die unbeabsichtigte Offenlegung interner Ressourcen wie Dateideskriptoren, Speicherreferenzen, Datenbankverbindungen oder anderer System-Handles an externe oder weniger privilegierte Prozesse. Wenn private Ressourcen über Vertrauensgrenzen hinweg lecken, können Angreifer unautorisierten Zugriff auf sensible Daten oder Fähigkeiten erlangen.

Risiko

Ressourcenlecks haben schwerwiegende Auswirkungen. Unautorisierter Zugriff auf sensible Dateien. Privilegieneskalation durch geerbte Deskriptoren. Datenoffenlegung an nicht vertrauenswürdige Prozesse. Umgehung von Zugriffskontrollen. Session-Hijacking. Diebstahl von Datenbankverbindungen. Speicheroffenlegung. Hohe Wahrscheinlichkeit wenn Prozesse Kinder ohne ordnungsgemäße Ressourcenbereinigung erzeugen.

Lösung

Schließen oder invalidieren Sie alle sensiblen Ressourcen bevor Kindprozesse während der Implementierungsphase erzeugt werden. Verwenden Sie Close-on-Exec-Flags für Dateideskriptoren. Implementieren Sie ordnungsgemäßes Ressourcen-Lebenszyklusmanagement. Bereinigen Sie die Umgebung vor der Prozesserstellung während der Architektur- und Design-Phase. Auditieren Sie Ressourcenbehandlung über Vertrauensgrenzen hinweg.

Häufige Auswirkungen

AuswirkungDetails
VertraulichkeitUmfang: Vertraulichkeit

Anwendungsdaten lesen - Angreifer können unautorisierten Zugriff auf sensible Informationen durch offengelegte Ressourcen erlangen.
IntegritätUmfang: Integrität

Modifikation von Daten durch geleckte Schreib-Handles.

Beispielcode

Anfälliger Code

// Anfällig: C-Code leckt Dateideskriptoren an Kindprozesse

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>

// ANFÄLLIG: Dateideskriptor wird von Kind geerbt
void vulnerable_spawn_child(const char* program) {
    // Sensible Datei öffnen
    int secret_fd = open("/etc/shadow", O_RDONLY);
    if (secret_fd < 0) {
        perror("open");
        return;
    }

    // ANFÄLLIG: Fork ohne Schließen des sensiblen fd
    pid_t pid = fork();

    if (pid == 0) {
        // Kindprozess - HAT NOCH ZUGRIFF auf secret_fd!
        // Auch wenn Kind weniger privilegiert sein kann

        // ANFÄLLIG: Nicht vertrauenswürdiges Programm mit gelecktem fd ausführen
        execl(program, program, NULL);
        exit(1);
    }

    // Eltern fährt fort...
    // Selbst wenn wir hier schließen, hat Kind bereits den fd
    close(secret_fd);

    waitpid(pid, NULL, 0);
}

// ANFÄLLIG: Datenbankverbindung leckt
void vulnerable_db_handler() {
    // Privilegierte Datenbankverbindung herstellen
    DB_CONN* conn = db_connect("admin", "password");

    // ANFÄLLIG: Verbindungs-Handle für Plugins zugänglich
    load_untrusted_plugin(conn);  // Plugin kann Admin-Verbindung nutzen

    db_disconnect(conn);
}

// ANFÄLLIG: Socket-Deskriptor-Leck
void vulnerable_server() {
    int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    bind(listen_fd, ...);
    listen(listen_fd, 10);

    while (1) {
        int client_fd = accept(listen_fd, NULL, NULL);

        pid_t pid = fork();
        if (pid == 0) {
            // Kind behandelt Client
            // ANFÄLLIG: listen_fd noch offen im Kind
            // Kind könnte neue Verbindungen akzeptieren!

            handle_client(client_fd);
            exit(0);
        }

        // Eltern sollte client_fd schließen aber listen_fd leckt zu Kind
        close(client_fd);
    }
}
# Anfällig: Python-Ressourcenlecks

import os
import subprocess

# ANFÄLLIG: Dateideskriptor-Leck zu Subprocess
def vulnerable_run_command(command):
    # Sensible Datei öffnen
    secret_file = open('/etc/shadow', 'r')

    # ANFÄLLIG: Subprocess erbt alle Dateideskriptoren
    result = subprocess.run(command, shell=True, capture_output=True)

    # Dateideskriptor war für Subprocess verfügbar
    secret_file.close()

    return result

# ANFÄLLIG: Datenbankverbindung leckt
class VulnerableApp:
    def __init__(self):
        # Privilegierte Datenbankverbindung
        self.db_conn = connect_database(admin=True)

    def run_plugin(self, plugin_code):
        # ANFÄLLIG: Plugin hat Zugriff auf self.db_conn
        exec(plugin_code)  # Kann auf self.db_conn zugreifen

# ANFÄLLIG: Temporäres Datei-Leck
def vulnerable_temp_file():
    import tempfile

    # Temp-Datei mit sensiblen Daten erstellen
    fd, path = tempfile.mkstemp()
    os.write(fd, b"geheime daten")

    # ANFÄLLIG: fd noch offen beim Aufrufen externen Prozesses
    os.system("some_command")  # Erbt den fd

    os.close(fd)
    os.unlink(path)

Korrigierter Code

// Korrigiert: Ordnungsgemäße Ressourcenverwaltung vor dem Erzeugen von Kindern

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>

// KORRIGIERT: Close-on-Exec-Flag verwenden
void safe_spawn_child(const char* program) {
    // KORRIGIERT: Mit O_CLOEXEC-Flag öffnen
    int secret_fd = open("/etc/shadow", O_RDONLY | O_CLOEXEC);
    if (secret_fd < 0) {
        perror("open");
        return;
    }

    // Dateideskriptor wird automatisch bei exec geschlossen
    pid_t pid = fork();

    if (pid == 0) {
        // Kindprozess
        // secret_fd wird geschlossen wenn execl aufgerufen wird

        execl(program, program, NULL);
        exit(1);
    }

    close(secret_fd);
    waitpid(pid, NULL, 0);
}

// KORRIGIERT: Expliziter Cleanup vor Fork
void safe_spawn_with_cleanup(const char* program) {
    int secret_fd = open("/etc/shadow", O_RDONLY);
    if (secret_fd < 0) {
        return;
    }

    // ... Datei verwenden ...

    // KORRIGIERT: Sensible Ressourcen vor Fork schließen
    close(secret_fd);
    secret_fd = -1;

    pid_t pid = fork();
    if (pid == 0) {
        // Kind hat keinen Zugriff auf secret_fd
        execl(program, program, NULL);
        exit(1);
    }

    waitpid(pid, NULL, 0);
}

// KORRIGIERT: Close-on-Exec nach Open setzen
void safe_set_cloexec(int fd) {
    int flags = fcntl(fd, F_GETFD);
    if (flags >= 0) {
        fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
    }
}

// KORRIGIERT: Server mit ordnungsgemäßer fd-Verwaltung
void safe_server() {
    int listen_fd = socket(AF_INET, SOCK_STREAM, 0);

    // KORRIGIERT: Close-on-Exec auf Listen-Socket setzen
    safe_set_cloexec(listen_fd);

    bind(listen_fd, ...);
    listen(listen_fd, 10);

    while (1) {
        int client_fd = accept(listen_fd, NULL, NULL);

        // KORRIGIERT: Close-on-Exec auch auf Client-fd setzen
        safe_set_cloexec(client_fd);

        pid_t pid = fork();
        if (pid == 0) {
            // KORRIGIERT: listen_fd im Kind schließen
            close(listen_fd);

            handle_client(client_fd);
            close(client_fd);
            exit(0);
        }

        // Eltern schließt client_fd
        close(client_fd);
    }
}

// KORRIGIERT: Alle unnötigen fds vor exec schließen
void close_all_fds_except(int keep_fd) {
    int max_fd = sysconf(_SC_OPEN_MAX);
    for (int fd = 3; fd < max_fd; fd++) {
        if (fd != keep_fd) {
            close(fd);
        }
    }
}
# Korrigiert: Python mit ordnungsgemäßer Ressourcenisolierung

import os
import subprocess

# KORRIGIERT: Dateideskriptoren vor Subprocess schließen
def safe_run_command(command):
    secret_file = open('/etc/shadow', 'r')
    secret_fd = secret_file.fileno()

    # ... Datei verwenden ...

    # KORRIGIERT: Vor Subprocess-Ausführung schließen
    secret_file.close()

    # KORRIGIERT: close_fds=True verwenden (Standard in Python 3)
    result = subprocess.run(
        command,
        shell=True,
        capture_output=True,
        close_fds=True  # KORRIGIERT: Alle fds außer stdin/stdout/stderr schließen
    )

    return result

# KORRIGIERT: Isolierte Datenbankverbindung
class SafeApp:
    def __init__(self):
        self._db_conn = None

    def _get_db_conn(self):
        """KORRIGIERT: Lazy Initialization, nicht exponiert."""
        if self._db_conn is None:
            self._db_conn = connect_database(admin=True)
        return self._db_conn

    def run_plugin(self, plugin_code):
        # KORRIGIERT: Begrenzte Verbindung für Plugin erstellen
        plugin_conn = connect_database(admin=False, read_only=True)

        # KORRIGIERT: Sandbox-Ausführung ohne Zugriff auf Admin-Verbindung
        sandbox = {'db': plugin_conn}
        exec(plugin_code, sandbox)

        plugin_conn.close()

# KORRIGIERT: Sichere temporäre Dateibehandlung
def safe_temp_file():
    import tempfile

    # Temp-Datei erstellen
    fd, path = tempfile.mkstemp()

    try:
        os.write(fd, b"geheime daten")

        # KORRIGIERT: Vor externem Befehl schließen
        os.close(fd)
        fd = -1

        # Jetzt sicher externen Prozess auszuführen
        subprocess.run(["some_command"], close_fds=True)
    finally:
        if fd >= 0:
            os.close(fd)
        os.unlink(path)

# KORRIGIERT: Kontextmanager für automatischen Cleanup
class SecureResource:
    def __init__(self, path):
        self.fd = os.open(path, os.O_RDONLY | os.O_CLOEXEC)

    def __enter__(self):
        return self

    def __exit__(self, *args):
        if self.fd >= 0:
            os.close(self.fd)
            self.fd = -1

# Verwendung
with SecureResource('/etc/shadow') as res:
    # Ressource verwenden
    pass
# Automatisch geschlossen, sicher Prozesse zu erzeugen

CVE-Beispiele

  • CVE-2003-0740: Server leckte privilegierten Dateideskriptor, der es Angreifern ermöglichte, die Verbindung zu kapern.
  • CVE-2004-1033: Dateideskriptor-Leck erlaubte unautorisierten Dateizugriff.
  • CVE-2003-0937: Dateideskriptor-Leck in Daemon erlaubte Privilegieneskalation.

Verwandte CWEs

  • CWE-668: Exposure of Resource to Wrong Sphere (Eltern)
  • CWE-403: Exposure of File Descriptor to Unintended Control Sphere (Kind)
  • CWE-619: Dangling Database Cursor (Kind)

Referenzen

  1. MITRE Corporation. "CWE-402: Transmission of Private Resources into a New Sphere." https://cwe.mitre.org/data/definitions/402.html
  2. CERT C. "FIO42-C. Close files when they are no longer needed"
  3. Linux man pages. "open(2) - O_CLOEXEC flag"