Speicherung sensibler Daten in nicht ordnungsgemäß gesperrtem Speicher

Beschreibung

Speicherung sensibler Daten in nicht ordnungsgemäß gesperrtem Speicher tritt auf, wenn eine Anwendung sensible Informationen (Passwörter, kryptografische Schlüssel, PII) in Speicher speichert, der nicht ordnungsgemäß gesperrt wurde, um das Auslagern auf die Festplatte zu verhindern. Wenn Speicher mit sensiblen Daten in den Swap-Bereich ausgelagert wird, persistieren die Daten auf der Festplatte in unverschlüsselter Form und sind potenziell für Angreifer mit Festplattenzugriff oder durch forensische Analyse zugänglich.

Risiko

Sensible Daten, die in den Swap geschrieben werden, können lange nach Beendigung der Anwendung persistieren. Angreifer mit physischem Zugriff oder administrativen Privilegien können Swap-Dateien/-Partitionen lesen, um Passwörter, Schlüssel oder andere Geheimnisse zu extrahieren. Die Anwendung scheint Daten sicher im Speicher zu behandeln, während sie tatsächlich Kopien auf der Festplatte hinterlässt. Cold-Boot-Angriffe können auch kürzlich ausgelagerte Speicherinhalte wiederherstellen. Dies untergräbt Annahmen über sichere Speicherbehandlung.

Lösung

Verwenden Sie plattformspezifische Speichersperr-APIs (mlock auf Unix, VirtualLock auf Windows), um zu verhindern, dass sensible Speicherbereiche ausgelagert werden. Stellen Sie sicher, dass Sperren erfolgreich sind, bevor Geheimnisse gespeichert werden. Löschen Sie sensible Daten sofort nach Verwendung. Verwenden Sie sichere Speicherallokatoren, die Sperrung automatisch behandeln. Auf Systemen, wo Sperrung nicht verfügbar ist, erwägen Sie verschlüsselten Swap oder minimieren Sie die Zeit, die sensible Daten im Speicher verbleiben.

Häufige Auswirkungen

AuswirkungDetails
VertraulichkeitBereich: Informationsoffenlegung

Sensible Daten, die in den Swap geschrieben werden, sind für Angreifer zugänglich.
SicherheitBereich: Anmeldedaten-Exposure

Passwörter und Schlüssel können von der Festplatte wiederhergestellt werden.
ComplianceBereich: Regulierungsverletzung

PII im Swap kann Datenschutzanforderungen verletzen.

Beispielcode + Lösungscode

Verwundbarer Code

// VERWUNDBAR: Sensible Daten nicht im Speicher gesperrt
void process_password_vulnerable(const char* user_input) {
    char password[256];
    strcpy(password, user_input);

    // Passwort in ungesperrtem Speicher - kann auf Festplatte ausgelagert werden!
    authenticate(password);

    // Selbst nach Löschen kann Swap Kopie behalten
    memset(password, 0, sizeof(password));
}

// VERWUNDBAR: Crypto-Schlüssel in regulärem Heap-Speicher
void use_crypto_key_vulnerable() {
    unsigned char* key = malloc(32);

    // Schlüssel in ungesperrtem Speicher generiert
    generate_key(key);

    // Kann während Verschlüsselung ausgelagert werden
    encrypt_data(key, data, data_len);

    memset(key, 0, 32);
    free(key);
}

// VERWUNDBAR: Annahme, Speicher sei sicher
struct Credentials {
    char username[64];
    char password[64];
    char api_key[128];
};

void store_credentials_vulnerable() {
    struct Credentials* creds = malloc(sizeof(struct Credentials));

    // Sensible Struktur in auslagerungsfähigem Speicher!
    load_credentials(creds);
    use_credentials(creds);

    memset(creds, 0, sizeof(struct Credentials));
    free(creds);
}

// VERWUNDBAR: Langlebige sensible Daten
static unsigned char master_key[32];  // Global - existiert gesamte Laufzeit

void init_crypto_vulnerable() {
    // Schlüssel in .bss-Segment - definitiv auslagerungsfähig
    derive_master_key(master_key);
    // Schlüssel sitzt in auslagerungsfähigem Speicher für gesamte Anwendungslebensdauer
}
// VERWUNDBAR: C++ mit ungesperrten sensiblen Daten
class VulnerableKeyStore {
    std::vector<uint8_t> masterKey;
    std::string password;

public:
    void setPassword(const std::string& pwd) {
        password = pwd;  // String in ungesperrtem Heap-Speicher
    }

    void setKey(const uint8_t* key, size_t len) {
        masterKey.assign(key, key + len);  // Vector in ungesperrtem Speicher
    }

    ~VulnerableKeyStore() {
        // Löschversuch, aber Swap kann Kopie haben
        std::fill(password.begin(), password.end(), '\0');
        std::fill(masterKey.begin(), masterKey.end(), 0);
    }
};

// VERWUNDBAR: Smart-Pointer auf sensible Daten
void process_sensitive_vulnerable() {
    auto secret = std::make_unique<char[]>(256);
    read_secret(secret.get());

    // unique_ptr verwendet regulären Heap - auslagerungsfähig
    process(secret.get());
}
# VERWUNDBAR: Python mit sensiblen Daten im Speicher
def process_password_vulnerable(password):
    # Python-Strings sind unveränderlich, Kopien überall
    # Alles in regulärem, auslagerungsfähigem Speicher
    hashed = hash_password(password)

    # Original-String kann nicht zuverlässig gelöscht werden
    return hashed

# VERWUNDBAR: Langlebige Geheimnisse
class VulnerableConfig:
    def __init__(self):
        self.api_key = None
        self.db_password = None

    def load_secrets(self):
        # Geheimnisse in reguläre Python-Objekte geladen
        # Können jederzeit ausgelagert werden
        self.api_key = read_from_vault("api_key")
        self.db_password = read_from_vault("db_password")

Lösungscode

#include <sys/mman.h>
#include <string.h>
#include <stdlib.h>

// SICHER: Speicher sperren vor Speicherung sensibler Daten
void process_password_safe(const char* user_input) {
    char password[256];

    // Speicher sperren um Auslagerung zu verhindern
    if (mlock(password, sizeof(password)) != 0) {
        // Fehler behandeln - Speicher kann nicht gesichert werden
        handle_error("Speicher könnte nicht gesperrt werden");
        return;
    }

    strcpy(password, user_input);
    authenticate(password);

    // Sensible Daten löschen
    explicit_bzero(password, sizeof(password));

    // Speicher entsperren
    munlock(password, sizeof(password));
}

// SICHER: Sichere Speicherallokation mit Sperrung
void* secure_alloc(size_t size) {
    void* ptr = malloc(size);
    if (ptr == NULL) {
        return NULL;
    }

    // Allozierten Speicher sperren
    if (mlock(ptr, size) != 0) {
        free(ptr);
        return NULL;
    }

    return ptr;
}

void secure_free(void* ptr, size_t size) {
    if (ptr == NULL) return;

    // Speicher löschen
    explicit_bzero(ptr, size);

    // Vor Freigabe entsperren
    munlock(ptr, size);

    free(ptr);
}

// SICHER: Crypto-Schlüssel in gesperrtem Speicher
void use_crypto_key_safe() {
    unsigned char* key = secure_alloc(32);
    if (key == NULL) {
        handle_error("Sicherer Speicher kann nicht allokiert werden");
        return;
    }

    generate_key(key);
    encrypt_data(key, data, data_len);

    secure_free(key, 32);
}

// SICHER: Credentials-Struktur mit ordnungsgemäßer Sperrung
struct SecureCredentials {
    char username[64];
    char password[64];
    char api_key[128];
};

struct SecureCredentials* create_secure_credentials() {
    struct SecureCredentials* creds = secure_alloc(sizeof(struct SecureCredentials));
    return creds;
}

void destroy_secure_credentials(struct SecureCredentials* creds) {
    secure_free(creds, sizeof(struct SecureCredentials));
}

// SICHER: mlockall für prozessweite Sperrung verwenden
void init_secure_process() {
    // Gesamten aktuellen und zukünftigen Speicher sperren
    if (mlockall(MCL_CURRENT | MCL_FUTURE) != 0) {
        // Benötigt möglicherweise CAP_IPC_LOCK oder Root-Privilegien
        handle_error("Gesamter Speicher kann nicht gesperrt werden");
    }
}
#include <sys/mman.h>
#include <cstring>
#include <memory>

// SICHER: Sicherer Allokator für STL-Container
template<typename T>
class SecureAllocator {
public:
    using value_type = T;

    SecureAllocator() = default;

    template<typename U>
    SecureAllocator(const SecureAllocator<U>&) {}

    T* allocate(size_t n) {
        size_t size = n * sizeof(T);
        T* ptr = static_cast<T*>(std::malloc(size));

        if (ptr && mlock(ptr, size) != 0) {
            std::free(ptr);
            throw std::bad_alloc();
        }

        return ptr;
    }

    void deallocate(T* ptr, size_t n) {
        if (ptr) {
            size_t size = n * sizeof(T);
            explicit_bzero(ptr, size);
            munlock(ptr, size);
            std::free(ptr);
        }
    }
};

// SICHER: Sicherer String-Typ
using SecureString = std::basic_string<char, std::char_traits<char>,
                                        SecureAllocator<char>>;

// SICHER: Sicherer Vector für Schlüssel
using SecureBytes = std::vector<uint8_t, SecureAllocator<uint8_t>>;

// SICHER: Key-Store mit gesperrtem Speicher
class SafeKeyStore {
    SecureBytes masterKey;
    SecureString password;

public:
    void setPassword(const std::string& pwd) {
        password.assign(pwd.begin(), pwd.end());
    }

    void setKey(const uint8_t* key, size_t len) {
        masterKey.assign(key, key + len);
    }

    // Destruktor löscht und entsperrt automatisch via SecureAllocator
};

// SICHER: RAII-Wrapper für gesperrten Speicher
class LockedMemory {
    void* ptr;
    size_t size;

public:
    explicit LockedMemory(size_t sz) : size(sz) {
        ptr = std::malloc(size);
        if (!ptr) throw std::bad_alloc();

        if (mlock(ptr, size) != 0) {
            std::free(ptr);
            throw std::runtime_error("Speicher könnte nicht gesperrt werden");
        }
    }

    ~LockedMemory() {
        if (ptr) {
            explicit_bzero(ptr, size);
            munlock(ptr, size);
            std::free(ptr);
        }
    }

    void* get() { return ptr; }

    // Kopieren verhindern
    LockedMemory(const LockedMemory&) = delete;
    LockedMemory& operator=(const LockedMemory&) = delete;
};
# SICHER: Verwendung sicherer Speicherbibliotheken
import ctypes
import mmap
import os

class SecureBuffer:
    """Buffer der versucht, Auslagerung zu verhindern"""

    def __init__(self, size):
        self.size = size
        # Seitenausgerichteten Speicher allokieren
        self.buffer = mmap.mmap(-1, size, mmap.MAP_PRIVATE | mmap.MAP_ANONYMOUS)

        # Versuchen Speicher zu sperren (erfordert Privilegien)
        try:
            import ctypes
            libc = ctypes.CDLL("libc.so.6", use_errno=True)
            addr = ctypes.c_void_p.from_buffer(self.buffer)
            if libc.mlock(addr, size) != 0:
                # Warnung protokollieren aber fortfahren
                pass
        except Exception:
            pass

    def write(self, data):
        self.buffer[:len(data)] = data

    def read(self):
        return bytes(self.buffer)

    def clear(self):
        # Mit Nullen überschreiben
        self.buffer[:] = b'\x00' * self.size

    def __del__(self):
        self.clear()
        self.buffer.close()

# SICHER: Spezialisierte Bibliotheken verwenden
from cryptography.hazmat.primitives import constant_time
import secrets

def process_password_safe(password: bytes) -> bytes:
    """Passwort mit sicheren Speicherpraktiken verarbeiten"""
    # Bibliothek verwenden die sicheren Speicher behandelt
    # cryptography-Bibliothek verwendet intern sicheren Speicher
    from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
    from cryptography.hazmat.primitives import hashes

    salt = secrets.token_bytes(16)
    kdf = PBKDF2HMAC(
        algorithm=hashes.SHA256(),
        length=32,
        salt=salt,
        iterations=100000,
    )

    return kdf.derive(password)

# SICHER: Keyring für Credential-Speicherung verwenden
import keyring

class SafeCredentialStore:
    """Credentials im OS-Keyring speichern (nicht im Python-Speicher)"""

    SERVICE_NAME = "myapp"

    def set_password(self, username: str, password: str):
        # Im sicheren OS-Speicher gespeichert, nicht Python-Speicher
        keyring.set_password(self.SERVICE_NAME, username, password)

    def get_password(self, username: str) -> str:
        return keyring.get_password(self.SERVICE_NAME, username)

    def delete_password(self, username: str):
        keyring.delete_password(self.SERVICE_NAME, username)

Ausgenutzt in der Praxis

Cold-Boot-Angriffe

Forscher demonstrierten die Wiederherstellung von Verschlüsselungsschlüsseln aus RAM durch Kühlen von Speicherchips und Lesen der Inhalte nach Neustart.

Swap-Datei-Analyse

Forensische Analyse von Swap-Dateien hat Passwörter, Verschlüsselungsschlüssel und sensible Dokumente offengelegt.

Virtual-Machine-Speicherabbilder

VM-Snapshots und Speicherabbilder haben Geheimnisse exponiert, die in ungesperrtem Gastspeicher gespeichert waren.


Tools zum Testen/Ausnutzen

  • Volatility — Speicherforensik-Framework
  • Cold Boot Tools — RAM-Forensik-Tools
  • Swap Digger — Linux-Swap-Analysetool
  • Mimikatz — Windows-Credential-Extraktion

CVE-Beispiele

  • CVEs mit Wiederherstellung von Credentials aus Swap-Space
  • Verschlüsselungsschlüssel-Exposure durch Speicherabbilder
  • VM-Snapshot-Angriffe, die sensible Daten exponieren

Referenzen

  1. MITRE. "CWE-591: Sensitive Data Storage in Improperly Locked Memory." https://cwe.mitre.org/data/definitions/591.html
  2. Cold Boot Attacks on Encryption Keys. Princeton/EFF Research.