Unvollständige Bereinigung

Beschreibung

Unvollständige Bereinigung ist eine Schwachstelle, bei der das Produkt temporäre oder unterstützende Ressourcen nach ihrer Verwendung nicht ordnungsgemäß bereinigt und entfernt. Wenn Anwendungen temporäre Dateien erstellen, Speicher allokieren, Netzwerkverbindungen öffnen oder andere Ressourcen erwerben, müssen sie diese Ressourcen ordnungsgemäß freigeben, wenn sie nicht mehr benötigt werden. Das Versäumnis kann zu Ressourcenerschöpfung, Informationspreisgabe durch übrig gebliebene Daten und Systeminstabilität führen. Dies ist besonders kritisch für temporäre Dateien, die sensible Informationen enthalten können.

Risiko

Unvollständige Bereinigung schafft mehrere Sicherheitsrisiken. Temporäre Dateien, die sensible Daten wie Datenbank-Anmeldeinformationen, Sitzungs-Token oder Verschlüsselungsschlüssel enthalten, können auf der Festplatte verbleiben und von Angreifern zugegriffen werden. Ressourcenerschöpfung tritt auf, wenn Anwendungen kontinuierlich Ressourcen allokieren, ohne sie freizugeben, was schließlich zu Denial-of-Service führt. Verzeichnisse haben Grenzen für Dateianzahlen, und ein Überlauf kann systemweite Ausfälle verursachen. In Multi-Tenant-Umgebungen können Restdaten aus einer Benutzersitzung für nachfolgende Benutzer zugänglich sein. NTFS Alternate Data Streams können Informationen auch nach scheinbarer Dateilöschung behalten.

Lösung

Löschen oder geben Sie temporäre Dateien und andere unterstützende Ressourcen sofort frei, nachdem sie nicht mehr benötigt werden. Verwenden Sie try-finally-Blöcke oder äquivalente Konstrukte, um sicherzustellen, dass Bereinigung auch bei Exceptions erfolgt. Erstellen Sie temporäre Dateien mit restriktiven Berechtigungen und in sicheren Verzeichnissen. Erwägen Sie die Verwendung von Memory-Mapped-Dateien oder In-Memory-Speicher für hochsensible temporäre Daten, die niemals die Festplatte berühren sollten. Implementieren Sie periodische Bereinigungs-Routinen, um Ressourcen abzufangen, die normalen Bereinigungspfaden entgehen. Verwenden Sie sichere Löschmethoden für sensible Daten, die Inhalte vor dem Löschen nullen.

Häufige Auswirkungen

AuswirkungDetails
VerfügbarkeitUmfang: Verfügbarkeit

DoS: Ressourcenverbrauch - Temporäre Dateien oder Ressourcen sammeln sich an und erschöpfen möglicherweise Festplattenplatz, Datei-Handles oder Speicher.
VertraulichkeitUmfang: Vertraulichkeit

Anwendungsdaten lesen - Sensible Informationen in temporären Dateien können unauthorisierten Akteuren offengelegt werden.
IntegritätUmfang: Integrität

Unerwarteter Zustand - Übrig gebliebene Ressourcen können bei nachfolgenden Ausführungen unerwarteten Anwendungszustand oder -verhalten verursachen.

Beispielcode

Anfälliger Code

// Anfällig: Stream wird bei Exception nicht geschlossen
public class VulnerableFileReader {

    public byte[] readFile(String path) {
        try {
            InputStream is = new FileInputStream(path);
            byte[] data = new byte[is.available()];
            is.read(data);
            is.close();  // Anfällig: Wird nicht aufgerufen wenn read() wirft
            return data;
        } catch (IOException e) {
            log.error("Lesen fehlgeschlagen: " + e.getMessage());
            return null;
            // Anfällig: Stream bleibt bei Exception offen
        }
    }

    // Anfällig: Temporäre Datei wird nicht gelöscht
    public void processLargeData(byte[] data) throws IOException {
        File tempFile = File.createTempFile("process_", ".tmp");
        FileOutputStream fos = new FileOutputStream(tempFile);
        fos.write(data);
        fos.close();

        processFile(tempFile);

        // Anfällig: tempFile wird nicht gelöscht
        // Anfällig: Was wenn processFile() wirft?
    }
}
# Anfällig: Ressourcen werden nicht bereinigt
import tempfile
import os

class VulnerableProcessor:
    def process_data(self, data):
        # Anfällig: Temp-Datei wird möglicherweise nicht gelöscht
        temp_fd, temp_path = tempfile.mkstemp(suffix='.dat')

        try:
            os.write(temp_fd, data)
            os.close(temp_fd)

            result = self.analyze_file(temp_path)

            # Anfällig: Wenn analyze_file wirft, wird Datei nie gelöscht
            os.unlink(temp_path)

            return result

        except Exception as e:
            # Anfällig: temp_path existiert noch auf Festplatte
            raise

    def create_session(self, user_id):
        # Anfällig: Session-Daten bleiben unbegrenzt bestehen
        session = {
            'user_id': user_id,
            'token': generate_token(),
            'created': time.time()
        }

        # Anfällig: Kein Bereinigungsmechanismus
        # Sessions sammeln sich für immer an
        self.sessions[session['token']] = session

        return session['token']
// Anfällig: Speicher- und Datei-Handle-Lecks
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char* vulnerable_process_file(const char* filename) {
    FILE* file = fopen(filename, "r");
    if (!file) {
        return NULL;
    }

    fseek(file, 0, SEEK_END);
    long size = ftell(file);
    fseek(file, 0, SEEK_SET);

    char* buffer = malloc(size + 1);
    if (!buffer) {
        // Anfällig: Datei-Handle geleakt
        return NULL;
    }

    if (fread(buffer, 1, size, file) != size) {
        // Anfällig: buffer geleakt, Datei-Handle geleakt
        return NULL;
    }

    buffer[size] = '\0';
    fclose(file);

    // Verarbeiten und Teilmenge zurückgeben
    char* result = extract_data(buffer);

    // Anfällig: Original-buffer wird nie freigegeben
    return result;
}

// Anfällig: Sensible Daten bleiben im Speicher
void process_password(const char* password) {
    char local_copy[256];
    strncpy(local_copy, password, sizeof(local_copy) - 1);

    hash_and_store(local_copy);

    // Anfällig: Passwort verbleibt in local_copy
    // Stack-Speicher nicht genullt, könnte wiederhergestellt werden
}
// Anfällig: Datenbankverbindungen werden nicht bereinigt
public class VulnerableDatabase {

    private List<SqlConnection> openConnections = new List<SqlConnection>();

    public DataTable ExecuteQuery(string query) {
        SqlConnection conn = new SqlConnection(connectionString);
        conn.Open();

        // Anfällig: Verbindung hinzugefügt aber nie entfernt
        openConnections.Add(conn);

        SqlCommand cmd = new SqlCommand(query, conn);
        SqlDataAdapter adapter = new SqlDataAdapter(cmd);
        DataTable results = new DataTable();
        adapter.Fill(results);

        // Anfällig: Verbindung nicht geschlossen
        // Anfällig: Command und Adapter nicht disposed

        return results;
    }

    // Anfällig: Temporäre Tabelle wird nicht bereinigt
    public void ProcessBatch(List<Record> records) {
        using (var conn = new SqlConnection(connectionString)) {
            conn.Open();

            // Temp-Tabelle erstellen
            ExecuteNonQuery(conn, "CREATE TABLE #TempBatch (Id INT, Data NVARCHAR(MAX))");

            foreach (var record in records) {
                InsertIntoTemp(conn, record);
            }

            ProcessTempTable(conn);

            // Anfällig: Temp-Tabelle wird nicht gelöscht
            // Session endet aber Tabelle kann in tempdb verbleiben
        }
    }
}

Korrigierter Code

// Korrigiert: Ordnungsgemäße Ressourcenbereinigung mit try-with-resources
public class SecureFileReader {

    public byte[] readFile(String path) {
        // Korrigiert: try-with-resources stellt Schließung sicher
        try (InputStream is = new FileInputStream(path)) {
            byte[] data = new byte[is.available()];
            is.read(data);
            return data;
        } catch (IOException e) {
            log.error("Lesen fehlgeschlagen: " + e.getMessage());
            return null;
        }
        // Stream wird automatisch geschlossen, auch bei Exception
    }

    public void processLargeData(byte[] data) throws IOException {
        // Korrigiert: Temp-Datei mit automatischer Löschung
        Path tempFile = Files.createTempFile("process_", ".tmp");

        try {
            Files.write(tempFile, data);
            processFile(tempFile.toFile());
        } finally {
            // Korrigiert: Temp-Datei immer löschen
            try {
                // Korrigiert: Sichere Löschung - vor Löschen überschreiben
                secureDelete(tempFile);
            } catch (IOException e) {
                log.warn("Temp-Datei löschen fehlgeschlagen: " + tempFile);
            }
        }
    }

    private void secureDelete(Path file) throws IOException {
        // Korrigiert: Mit Nullen überschreiben vor Löschung
        long size = Files.size(file);
        try (FileChannel channel = FileChannel.open(file, StandardOpenOption.WRITE)) {
            ByteBuffer zeros = ByteBuffer.allocate(8192);
            long remaining = size;
            while (remaining > 0) {
                zeros.clear();
                int toWrite = (int) Math.min(remaining, zeros.capacity());
                zeros.limit(toWrite);
                channel.write(zeros);
                remaining -= toWrite;
            }
        }
        Files.delete(file);
    }
}
# Korrigiert: Ordnungsgemäße Ressourcenbereinigung
import tempfile
import os
import contextlib
import time

class SecureProcessor:
    def process_data(self, data):
        # Korrigiert: Context-Manager für Temp-Datei verwenden
        with tempfile.NamedTemporaryFile(suffix='.dat', delete=False) as temp:
            temp_path = temp.name
            temp.write(data)

        try:
            result = self.analyze_file(temp_path)
            return result
        finally:
            # Korrigiert: Immer bereinigen
            self._secure_delete(temp_path)

    def _secure_delete(self, path):
        """Datei sicher löschen durch Überschreiben dann Entfernen."""
        try:
            # Korrigiert: Mit Zufallsdaten überschreiben
            file_size = os.path.getsize(path)
            with open(path, 'wb') as f:
                f.write(os.urandom(file_size))
            os.unlink(path)
        except OSError as e:
            logger.warning(f"Sicheres Löschen von {path} fehlgeschlagen: {e}")

    def create_session(self, user_id, timeout_seconds=3600):
        session = {
            'user_id': user_id,
            'token': generate_token(),
            'created': time.time(),
            'expires': time.time() + timeout_seconds
        }

        self.sessions[session['token']] = session

        # Korrigiert: Bereinigung planen
        self._schedule_session_cleanup(session['token'], timeout_seconds)

        return session['token']

    def cleanup_expired_sessions(self):
        """Korrigiert: Periodische Bereinigung abgelaufener Sessions."""
        now = time.time()
        expired = [
            token for token, session in self.sessions.items()
            if session['expires'] < now
        ]
        for token in expired:
            del self.sessions[token]
            logger.info(f"Abgelaufene Session bereinigt: {token[:8]}...")
// Korrigiert: Ordnungsgemäße Speicher- und Datei-Handle-Bereinigung
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    char* data;
    int error;
    char error_msg[256];
} FileResult;

FileResult secure_process_file(const char* filename) {
    FileResult result = {NULL, 0, ""};
    FILE* file = NULL;
    char* buffer = NULL;

    file = fopen(filename, "r");
    if (!file) {
        result.error = 1;
        snprintf(result.error_msg, sizeof(result.error_msg),
                 "Datei kann nicht geöffnet werden");
        goto cleanup;
    }

    fseek(file, 0, SEEK_END);
    long size = ftell(file);
    fseek(file, 0, SEEK_SET);

    buffer = malloc(size + 1);
    if (!buffer) {
        result.error = 2;
        snprintf(result.error_msg, sizeof(result.error_msg),
                 "Speicherallokation fehlgeschlagen");
        goto cleanup;  // Korrigiert: Datei wird geschlossen
    }

    if (fread(buffer, 1, size, file) != size) {
        result.error = 3;
        snprintf(result.error_msg, sizeof(result.error_msg),
                 "Lesen fehlgeschlagen");
        goto cleanup;  // Korrigiert: Beide werden bereinigt
    }

    buffer[size] = '\0';
    result.data = extract_data(buffer);

cleanup:
    // Korrigiert: Ressourcen immer bereinigen
    if (buffer) {
        // Korrigiert: Vor Freigabe nullen
        memset(buffer, 0, size);
        free(buffer);
    }
    if (file) {
        fclose(file);
    }

    return result;
}

// Korrigiert: Sensible Daten nach Verwendung genullt
void process_password(const char* password) {
    char local_copy[256];
    strncpy(local_copy, password, sizeof(local_copy) - 1);
    local_copy[sizeof(local_copy) - 1] = '\0';

    hash_and_store(local_copy);

    // Korrigiert: Passwort im Speicher nullen
    explicit_bzero(local_copy, sizeof(local_copy));
}
// Korrigiert: Ordnungsgemäße Datenbankverbindungs-Bereinigung
public class SecureDatabase : IDisposable {

    private bool disposed = false;

    public DataTable ExecuteQuery(string query) {
        // Korrigiert: Using-Statement stellt Disposal sicher
        using (SqlConnection conn = new SqlConnection(connectionString))
        using (SqlCommand cmd = new SqlCommand(query, conn))
        using (SqlDataAdapter adapter = new SqlDataAdapter(cmd)) {
            conn.Open();

            DataTable results = new DataTable();
            adapter.Fill(results);

            return results;
        }
        // Alle Ressourcen automatisch bereinigt
    }

    public void ProcessBatch(List<Record> records) {
        using (var conn = new SqlConnection(connectionString)) {
            conn.Open();

            try {
                ExecuteNonQuery(conn, "CREATE TABLE #TempBatch (Id INT, Data NVARCHAR(MAX))");

                foreach (var record in records) {
                    InsertIntoTemp(conn, record);
                }

                ProcessTempTable(conn);
            }
            finally {
                // Korrigiert: Temp-Tabelle immer löschen
                try {
                    ExecuteNonQuery(conn, "DROP TABLE IF EXISTS #TempBatch");
                }
                catch (SqlException) {
                    // Temp-Tabelle könnte bereits gelöscht oder nie erstellt worden sein
                }
            }
        }
    }

    public void Dispose() {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing) {
        if (!disposed) {
            if (disposing) {
                // Korrigiert: Verwaltete Ressourcen bereinigen
                CleanupAllConnections();
            }
            disposed = true;
        }
    }
}

CVE-Beispiele

  • CVE-2000-0552 - Weltweit lesbare temporäre Datei blieb nach Verwendung bestehen und legte sensible Daten offen.
  • CVE-2005-2293 - Nicht gelöschte temporäre Datei leakte Datenbank-Anmeldeinformationen.
  • CVE-2002-2066 - NTFS Alternate Data Streams behielten Daten nach Dateibereinigung.
  • CVE-2002-2067 - Dateibereinigungsprogramm entfernte NTFS Alternate Data Streams nicht.
  • CVE-2002-2068 - Unvollständige Bereinigung von NTFS Alternate Data Streams.
  • CVE-2002-2069 - Restdaten in NTFS-Streams nach Löschung verblieben.
  • CVE-2002-2070 - Datenreste in NTFS Alternate Streams blieben bestehen.

Referenzen

  1. MITRE Corporation. "CWE-459: Incomplete Cleanup." https://cwe.mitre.org/data/definitions/459.html
  2. CERT C Secure Coding Standard. "MEM00-C. Allocate and free memory in the same module, at the same level of abstraction."