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
| Auswirkung | Details |
|---|---|
| Vertraulichkeit | Bereich: 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
-
MITRE Corporation. "CWE-316: Cleartext Storage of Sensitive Information in Memory." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/316.html
-
CERT/CC. "MEM03-C: Clear sensitive information stored in reusable resources." https://wiki.sei.cmu.edu/confluence/display/c/MEM03-C
-
OWASP Foundation. "Memory Management Cheat Sheet." https://cheatsheetseries.owasp.org/