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
| Auswirkung | Details |
|---|---|
| Vertraulichkeit | Bereich: Informationsoffenlegung Sensible Daten, die in den Swap geschrieben werden, sind für Angreifer zugänglich. |
| Sicherheit | Bereich: Anmeldedaten-Exposure Passwörter und Schlüssel können von der Festplatte wiederhergestellt werden. |
| Compliance | Bereich: 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
- MITRE. "CWE-591: Sensitive Data Storage in Improperly Locked Memory." https://cwe.mitre.org/data/definitions/591.html
- Cold Boot Attacks on Encryption Keys. Princeton/EFF Research.