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
| Auswirkung | Details |
|---|---|
| Verfügbarkeit | Umfang: Verfügbarkeit DoS: Ressourcenverbrauch - Temporäre Dateien oder Ressourcen sammeln sich an und erschöpfen möglicherweise Festplattenplatz, Datei-Handles oder Speicher. |
| Vertraulichkeit | Umfang: Vertraulichkeit Anwendungsdaten lesen - Sensible Informationen in temporären Dateien können unauthorisierten Akteuren offengelegt werden. |
| Integrität | Umfang: 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
- MITRE Corporation. "CWE-459: Incomplete Cleanup." https://cwe.mitre.org/data/definitions/459.html
- CERT C Secure Coding Standard. "MEM00-C. Allocate and free memory in the same module, at the same level of abstraction."