Klartext-Speicherung sensibler Informationen im Speicher

Beschreibung

Klartext-Speicherung sensibler Informationen im Speicher ist eine Schwachstelle, die auftritt, wenn ein Produkt sensible Informationen im Klartext im Arbeitsspeicher speichert. Obwohl Speicher im Allgemeinen als sicherer als persistente Speicherung gilt, können sensible Daten im Speicher durch mehrere Vektoren exponiert werden: Memory-Dumps bei Abstürzen (Core-Dumps), Speicher-Swapping auf Festplatte, Hibernation-Dateien, Debug-Operationen, Speicher-Scanning durch Malware, Cold-Boot-Angriffe und Ausnutzung anderer speicherbezogener Schwachstellen. Sensible Daten, die länger als nötig im Speicher verbleiben, erhöhen das Zeitfenster für diese Angriffsvektoren.

Risiko

Klartext-Speicherspeicherung exponiert sensible Daten durch mehrere Angriffsszenarien. Core-Dumps, die bei Anwendungsabstürzen generiert werden, können Passwörter, Verschlüsselungsschlüssel und andere sensible Daten enthalten, die im Speicher gespeichert sind. Wenn Systeme Speicherseiten aufgrund von Speicherdruck auf die Festplatte auslagern, können sensible Daten in ungeschützte Swap-Partitionen geschrieben werden. Hibernation- und Sleep-Modi schreiben Speicherinhalte auf die Festplatte, was potenziell alle In-Memory-Secrets exponiert. Debug-Szenarien (sowohl legitime als auch durch Debug-Interface-Ausnutzung) ermöglichen Speicherinspektion. Speziell für Speicher-Scanning entwickelte Malware kann Anmeldedaten und Schlüssel extrahieren. Physische Angriffe wie Cold-Boot-Angriffe können Speicherinhalte von kürzlich ausgeschalteten Systemen wiederherstellen. Speicherforensik auf kompromittierten Systemen kann sensible Daten offenbaren. Das Risiko wird verstärkt, wenn Anwendungen sensible Daten nach der Verwendung nicht löschen und sie für längere Zeiträume zugänglich lassen.

Lösung

Minimieren Sie die Dauer, in der sensible Daten im Klartext im Speicher existieren. Verwenden Sie sichere Speicherallokation, die Swapping auf Festplatte verhindert, wo verfügbar. Löschen Sie sensible Daten explizit aus dem Speicher unmittelbar nach der Verwendung durch Überschreiben mit Nullen oder Zufallsdaten vor der Freigabe. Verwenden Sie Sprach- oder Plattformfunktionen, die für sensible Datenhandhabung konzipiert sind (SecureString in .NET, explicit_bzero in C). Implementieren Sie Verschlüsselung auf Anwendungsebene für sensible Daten, wenn sie im Speicher persistieren müssen. Konfigurieren Sie Systeme so, dass Core-Dumps in der Produktion deaktiviert oder verschlüsselt werden. Verwenden Sie verschlüsselte Swap-Partitionen. Deaktivieren Sie Hibernation auf Systemen, die sensible Daten verarbeiten. Implementieren Sie Speicherschutzmechanismen, wo verfügbar. Beachten Sie, dass Standard-Speicherlöschung von Compilern wegoptimiert werden kann - verwenden Sie dafür vorgesehene sichere Löschfunktionen. Erwägen Sie die Verwendung von Hardware-Sicherheitsmodulen (HSMs) für die sensibelsten kryptografischen Operationen.

Häufige Auswirkungen

AuswirkungDetails
VertraulichkeitBereich: Vertraulichkeit

Angreifer, die durch Dumps, Swapping, Debugging oder Speicher-Scanning auf den Speicher zugreifen können, können sensible Informationen einschließlich Anmeldedaten, Verschlüsselungsschlüssel, persönliche Daten und Session-Token lesen.

Beispielcode

Anfälliger Code (C/C++)

Die folgenden Beispiele demonstrieren Klartext-Speicherspeicherungs-Schwachstellen:

// Anfällig: Passwort bleibt nach Verwendung im Speicher
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int vulnerable_authenticate(const char *username) {
    char password[256];

    printf("Passwort eingeben: ");
    fgets(password, sizeof(password), stdin);
    password[strcspn(password, "\n")] = 0;

    int result = check_credentials(username, password);

    // Anfällig: Passwort verbleibt im Speicher!
    // free() oder return löscht die Daten nicht

    return result;
}

// Anfällig: Verschlüsselungsschlüssel verbleibt im Speicher
typedef struct {
    unsigned char key[32];
    unsigned char iv[16];
} EncryptionContext;

void vulnerable_encrypt(const char *data, size_t len) {
    EncryptionContext ctx;

    // Schlüssel in Speicher laden
    load_encryption_key(ctx.key, sizeof(ctx.key));
    generate_iv(ctx.iv, sizeof(ctx.iv));

    // Verschlüsselung durchführen
    encrypt_data(&ctx, data, len);

    // Anfällig: Schlüssel und IV verbleiben in ctx auf Stack
    // Könnten in Core-Dump oder Stack-Inspektion exponiert werden
}

// Anfällig: Dynamisch allokierte sensible Daten
char* vulnerable_get_api_key() {
    char *api_key = malloc(64);
    strcpy(api_key, "sk_live_secret_key_12345");

    // Schlüssel verwenden...
    make_api_call(api_key);

    // Anfällig: Nur freigeben löscht Speicher nicht
    free(api_key);
    // Speicher enthält noch Schlüssel bis überschrieben!

    return NULL;
}
# Anfällig: Python-Passworthandhabung
def vulnerable_login(username):
    password = input("Passwort eingeben: ")

    # Passwort ist ein String-Objekt im Speicher
    result = authenticate(username, password)

    # Anfällig: Passwort kann nicht einfach aus Speicher gelöscht werden
    # Python-Strings sind unveränderlich, del entfernt nur Referenz
    del password
    # String kann noch im Speicher existieren bis Garbage-Collected

    return result

# Anfällig: Sensible Daten in Variablen speichern
class VulnerableConfig:
    def __init__(self):
        self.db_password = None
        self.api_secret = None

    def load_config(self):
        # Anfällig: Sensible Daten bleiben im Objektspeicher
        self.db_password = os.environ.get('DB_PASSWORD')
        self.api_secret = os.environ.get('API_SECRET')
        # Diese verbleiben im Speicher für Objektlebensdauer

    def connect(self):
        # Verwendet Klartext-Anmeldedaten aus Speicher
        return db_connect(self.db_user, self.db_password)
// Anfällig: C#-Passworthandhabung
using System;

public class VulnerableAuth
{
    public bool Authenticate(string username)
    {
        // Anfällig: String ist unveränderlich, kann nicht gelöscht werden
        string password = Console.ReadLine();

        bool result = CheckCredentials(username, password);

        // Dies löscht den String nicht aus dem Speicher
        password = null;
        GC.Collect();  // Garantiert immer noch keine Löschung

        return result;
    }

    // Anfällig: Anmeldedaten in regulären Strings speichern
    private string _apiKey;

    public void LoadApiKey()
    {
        _apiKey = File.ReadAllText("api_key.txt");
        // Schlüssel verbleibt als unveränderlicher String im Speicher
    }
}

Korrigierter Code (C/C++)

// Korrigiert: Sichere Speicherhandhabung für sensible Daten
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#ifdef _WIN32
#include <windows.h>
#define secure_zero(ptr, size) SecureZeroMemory(ptr, size)
#else
// explicit_bzero oder volatile write verwenden um Optimierung zu verhindern
void secure_zero(void *ptr, size_t size) {
    volatile unsigned char *p = ptr;
    while (size--) {
        *p++ = 0;
    }
}
#endif

int secure_authenticate(const char *username) {
    char password[256];

    printf("Passwort eingeben: ");
    fgets(password, sizeof(password), stdin);
    password[strcspn(password, "\n")] = 0;

    int result = check_credentials(username, password);

    // Korrigiert: Passwort sicher aus Speicher löschen
    secure_zero(password, sizeof(password));

    return result;
}

// Korrigiert: Sichere Verschlüsselungsschlüssel-Handhabung
typedef struct {
    unsigned char key[32];
    unsigned char iv[16];
} EncryptionContext;

void secure_encrypt(const char *data, size_t len) {
    EncryptionContext ctx;

    // Speicher sperren um Swapping zu verhindern (erfordert Privilegien)
    #ifndef _WIN32
    mlock(&ctx, sizeof(ctx));
    #else
    VirtualLock(&ctx, sizeof(ctx));
    #endif

    // Schlüssel in Speicher laden
    load_encryption_key(ctx.key, sizeof(ctx.key));
    generate_iv(ctx.iv, sizeof(ctx.iv));

    // Verschlüsselung durchführen
    encrypt_data(&ctx, data, len);

    // Korrigiert: Sensible Daten sicher löschen
    secure_zero(ctx.key, sizeof(ctx.key));
    secure_zero(ctx.iv, sizeof(ctx.iv));

    // Speicher entsperren
    #ifndef _WIN32
    munlock(&ctx, sizeof(ctx));
    #else
    VirtualUnlock(&ctx, sizeof(ctx));
    #endif
}

// Korrigiert: Sichere dynamische Speicherhandhabung
typedef struct {
    char *data;
    size_t len;
} SecureBuffer;

SecureBuffer* secure_alloc(size_t size) {
    SecureBuffer *buf = malloc(sizeof(SecureBuffer));
    if (!buf) return NULL;

    buf->data = malloc(size);
    if (!buf->data) {
        free(buf);
        return NULL;
    }

    buf->len = size;

    // Sperren um Swapping zu verhindern
    #ifndef _WIN32
    mlock(buf->data, size);
    #endif

    return buf;
}

void secure_free(SecureBuffer *buf) {
    if (buf) {
        if (buf->data) {
            // Korrigiert: Vor Freigabe löschen
            secure_zero(buf->data, buf->len);
            #ifndef _WIN32
            munlock(buf->data, buf->len);
            #endif
            free(buf->data);
        }
        free(buf);
    }
}

char* secure_get_api_key() {
    SecureBuffer *key_buf = secure_alloc(64);
    if (!key_buf) return NULL;

    strcpy(key_buf->data, "sk_live_secret_key_12345");

    // Schlüssel verwenden...
    make_api_call(key_buf->data);

    // Korrigiert: Sicher löschen und freigeben
    secure_free(key_buf);

    return NULL;
}
// Korrigiert: C# sichere Speicherhandhabung
using System;
using System.Security;
using System.Runtime.InteropServices;

public class SecureAuth
{
    public bool Authenticate(string username)
    {
        // Korrigiert: SecureString für Passwort verwenden
        SecureString password = new SecureString();

        ConsoleKeyInfo keyInfo;
        while ((keyInfo = Console.ReadKey(true)).Key != ConsoleKey.Enter)
        {
            password.AppendChar(keyInfo.KeyChar);
        }
        password.MakeReadOnly();

        bool result = CheckCredentialsSecure(username, password);

        // Korrigiert: Dispose löscht den Speicher
        password.Dispose();

        return result;
    }

    private bool CheckCredentialsSecure(string username, SecureString password)
    {
        IntPtr passwordPtr = IntPtr.Zero;
        try
        {
            // Für API-Aufruf in unverwalteten Speicher konvertieren
            passwordPtr = Marshal.SecureStringToGlobalAllocUnicode(password);
            string plainPassword = Marshal.PtrToStringUni(passwordPtr);

            // Anmeldedaten prüfen
            bool result = VerifyPassword(username, plainPassword);

            return result;
        }
        finally
        {
            // Korrigiert: Unverwalteten Speicher löschen
            if (passwordPtr != IntPtr.Zero)
            {
                Marshal.ZeroFreeGlobalAllocUnicode(passwordPtr);
            }
        }
    }

    // Korrigiert: Sichere Byte-Array-Handhabung
    public byte[] ProcessSensitiveData(byte[] sensitiveInput)
    {
        byte[] workingCopy = new byte[sensitiveInput.Length];
        byte[] result;

        try
        {
            Array.Copy(sensitiveInput, workingCopy, sensitiveInput.Length);

            // Daten verarbeiten...
            result = Transform(workingCopy);
        }
        finally
        {
            // Korrigiert: Sensible Daten löschen
            Array.Clear(workingCopy, 0, workingCopy.Length);
            Array.Clear(sensitiveInput, 0, sensitiveInput.Length);
        }

        return result;
    }
}
# Korrigiert: Python sichere Speicherhandhabung (eingeschränkt)
import ctypes
import gc

def secure_zero_string(s):
    """Versuch String aus Speicher zu löschen (eingeschränkte Wirksamkeit)."""
    # Hinweis: Dies ist Best-Effort aufgrund von Pythons String-Interning
    try:
        location = id(s) + 20  # Offset zu String-Daten
        size = len(s)
        ctypes.memset(location, 0, size)
    except:
        pass

def secure_zero_bytearray(ba):
    """Bytearray-Inhalte löschen."""
    for i in range(len(ba)):
        ba[i] = 0

# Korrigiert: Bytearray für veränderliche sensible Daten verwenden
def secure_login(username):
    # Bytearray anstelle von String verwenden (veränderlich)
    password = bytearray(input("Passwort eingeben: "), 'utf-8')

    try:
        result = authenticate(username, password.decode('utf-8'))
    finally:
        # Korrigiert: Bytearray löschen
        secure_zero_bytearray(password)
        gc.collect()

    return result

# Korrigiert: Begrenzte Lebensdauer für sensible Konfiguration
class SecureConfig:
    def __init__(self):
        self._encrypted_password = None
        self._key = None

    def connect(self):
        # Nur bei Bedarf entschlüsseln, sofort nach Verwendung löschen
        password = self._decrypt(self._encrypted_password, self._key)
        try:
            conn = db_connect(self.db_user, password)
        finally:
            if isinstance(password, bytearray):
                secure_zero_bytearray(password)
        return conn

Die Korrektur implementiert sicheres Speicherlöschen, Speichersperrung und verwendet sprachspezifische sichere Datentypen.


Ausgenutzt in der Praxis

SSH-Client-Speicherexposition (Sicherheitssoftware, 2003)

CVE-2003-0291 dokumentierte einen SSH-Client, der Anmeldedaten nach der Authentifizierung nicht aus dem Speicher löschte, was Speicherinspektion ermöglichte, um Passwörter offenzulegen.

Passwort-Manager-Speicherschwachstellen (Sicherheitssoftware, 2001)

CVE-2001-0984 dokumentierte eine Passwortanwendung, die Speicher beim Minimieren nicht löschte, trotz Benutzereinstellungen dies zu tun.


Tools zum Testen/Ausnutzen

  • Volatility — Speicherforensik-Framework zur Analyse von Memory-Dumps.

  • Process Hacker — Tool zur Inspektion von Prozessspeicher.

  • WinDbg — Windows-Debugger für Speicheranalyse.


CVE-Beispiele

  • CVE-2001-1517 — Authentifizierungsinformationen im Klartext im Speicher.

  • CVE-2001-0984 — Passwortanwendung löscht Speicher nicht.

  • CVE-2003-0291 — SSH-Client löscht Anmeldedaten nicht aus Speicher.


Referenzen

  1. MITRE Corporation. "CWE-316: Cleartext Storage of Sensitive Information in Memory." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/316.html

  2. CERT/CC. "MEM03-C: Clear sensitive information stored in reusable resources." https://wiki.sei.cmu.edu/confluence/display/c/MEM03-C

  3. OWASP Foundation. "Memory Management Cheat Sheet." https://cheatsheetseries.owasp.org/