Klartext-Speicherung sensibler Informationen in ausführbaren Dateien
Beschreibung
Klartext-Speicherung sensibler Informationen in ausführbaren Dateien ist eine Schwachstelle, die auftritt, wenn ein Produkt sensible Informationen im Klartext innerhalb einer ausführbaren Binärdatei speichert. Dies umfasst Passwörter, Verschlüsselungsschlüssel, API-Schlüssel, private Schlüssel und andere Geheimnisse, die direkt in kompilierten Code eingebettet oder in Datensektionen von ausführbaren Dateien gespeichert werden. Angreifer können diese Informationen durch Reverse Engineering, Binäranalyse oder einfache String-Extraktion aus der ausführbaren Datei extrahieren. Selbst wenn Informationen kodiert sind (wie Base64), bietet dies keinen sinnvollen Schutz, da Angreifer gängige Kodierungsschemata leicht identifizieren und dekodieren können.
Risiko
Das Einbetten sensibler Informationen in ausführbare Dateien erzeugt schwere, dauerhafte Sicherheitslücken. Die verteilte Natur ausführbarer Dateien bedeutet, dass Geheimnisse jedem mit Zugriff auf die Binärdatei offengelegt werden. Reverse-Engineering-Tools und -Techniken sind weit verbreitet, was die Extraktion selbst für mäßig erfahrene Angreifer unkompliziert macht. Einfache String-Suchen offenbaren oft Passwörter und Schlüssel, die als ASCII-Text gespeichert sind. Eingebettete Geheimnisse können nicht rotiert werden, ohne die gesamte Anwendung neu zu verteilen. Die Schwachstelle betrifft alle Instanzen der Anwendung - eine einzige Binäranalyse kompromittiert jede Bereitstellung. Reale Exploits umfassten eingebettete RSA-Privatschlüssel, die Server-Spoofing ermöglichten, hartcodierte Administrationspasswörter und SSH-Schlüssel, die Root-Zugriff gewährten. Die Auswirkung wird verstärkt, wenn ausführbare Dateien an nicht vertrauenswürdige Parteien verteilt oder öffentlich verfügbar gemacht werden.
Lösung
Betten Sie niemals sensible Informationen direkt in ausführbaren Code oder Ressourcen ein. Verwenden Sie externe Konfigurationsdateien mit ordnungsgemäßen Zugriffskontrollen und Verschlüsselung. Implementieren Sie sichere Schlüsselverwaltungssysteme, die Schlüssel zur Laufzeit statt zur Kompilierzeit bereitstellen. Verwenden Sie Umgebungsvariablen für Anmeldedaten in Bereitstellungsumgebungen. Implementieren Sie Hardware-Sicherheitsmodule (HSMs) für kryptografische Schlüsselspeicherung in hochsicheren Anwendungen. Für Anwendungen, die eingebettete Authentifizierung erfordern, verwenden Sie Challenge-Response-Protokolle anstelle statischer Geheimnisse. Wenn irgendeine Form von eingebettetem Geheimnis unvermeidbar ist, verwenden Sie White-Box-Kryptografie-Techniken zur Verschleierung von Schlüsseln, wobei zu beachten ist, dass dies nur Defense in Depth und keine echte Sicherheit bietet. Implementieren Sie Code-Obfuskation als zusätzliche Schicht, verlassen Sie sich aber niemals allein darauf. Verwenden Sie Binär-Scan-Tools während CI/CD, um versehentlich eingebettete Geheimnisse zu erkennen.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Vertraulichkeit | Umfang: Vertraulichkeit Angreifer können sensible Informationen aus ausführbaren Dateien durch Reverse Engineering oder String-Analyse extrahieren, wodurch Verschlüsselungsschlüssel, Passwörter, API-Schlüssel und andere Geheimnisse kompromittiert werden. |
| Zugriffskontrolle, Authentifizierung | Umfang: Zugriffskontrolle, Authentifizierung Offengelegte Anmeldedaten ermöglichen unbefugten Zugriff. Kompromittierte private Schlüssel erlauben Server-Imitation und Man-in-the-Middle-Angriffe. Alle Bereitstellungen mit derselben ausführbaren Datei sind gleichermaßen kompromittiert. |
Beispielcode
Anfälliger Code (C/Java)
Die folgenden Beispiele demonstrieren Schwachstellen bei der Klartext-Speicherung in ausführbaren Dateien:
// Anfällig: Hartcodierte Anmeldedaten in C-ausführbarer Datei
#include <stdio.h>
#include <string.h>
// Anfällig: Passwort als String-Konstante in Binärdatei
#define ADMIN_PASSWORD "SuperSecretAdmin123"
// Anfällig: Verschlüsselungsschlüssel im Code eingebettet
static const unsigned char encryption_key[] = {
0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6,
0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c
};
// Anfällig: API-Schlüssel als String
static const char *API_KEY = "sk_live_51H2x3y4z5a6b7c8d9e0f1g2h3";
// Anfällig: Datenbank-Anmeldedaten eingebettet
static const char *DB_CONNECTION =
"Server=prod.db.example.com;User=admin;Password=DbP@ss2024!;";
int authenticate(const char *password) {
// Anfällig: Vergleich mit eingebettetem Passwort
if (strcmp(password, ADMIN_PASSWORD) == 0) {
return 1; // Authentifiziert
}
return 0;
}
void encrypt_data(unsigned char *data, size_t len) {
// Anfällig: Verwendung des eingebetteten Schlüssels
aes_encrypt(data, len, encryption_key);
}
// Ausführen von 'strings' auf dieser Binärdatei offenbart:
// SuperSecretAdmin123
// sk_live_51H2x3y4z5a6b7c8d9e0f1g2h3
// Server=prod.db.example.com;User=admin;Password=DbP@ss2024!;
// Anfällig: Hartcodierte Anmeldedaten in Java
public class VulnerableConfig {
// Anfällig: Passwort als Konstante
private static final String ADMIN_PASSWORD = "AdminSecret2024!";
// Anfällig: API-Schlüssel in Klasse eingebettet
private static final String STRIPE_API_KEY = "sk_live_abcd1234efgh5678";
// Anfällig: Datenbank-Anmeldedaten
private static final String DB_URL =
"jdbc:mysql://prod.db.example.com/app";
private static final String DB_USER = "root";
private static final String DB_PASSWORD = "RootP@ssw0rd!";
// Anfällig: Verschlüsselungsschlüssel als Byte-Array
private static final byte[] AES_KEY = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
};
// Anfällig: Vergleich mit hartcodiertem Passwort
public boolean authenticate(String password) {
// Hash-Vergleich hilft nicht - Hash ist auch eingebettet!
if (password.equals("68af404b513073584c4b6f22b6c63e6b")) {
return true;
}
return false;
}
public Connection getConnection() throws SQLException {
// Anfällig: Verwendung eingebetteter Anmeldedaten
return DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);
}
}
# Anfällig: Python-Skript zu ausführbarer Datei kompiliert
# (mit PyInstaller, cx_Freeze, etc.)
# Anfällig: Hartcodierte Anmeldedaten
AWS_ACCESS_KEY = "AKIAIOSFODNN7EXAMPLE"
AWS_SECRET_KEY = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
# Anfällig: Eingebetteter RSA-Privatschlüssel
RSA_PRIVATE_KEY = """
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA0Z3VS5JJcds3xfn/ygWyf8Lsh...
-----END RSA PRIVATE KEY-----
"""
# Anfällig: Datenbankpasswort
DB_CONFIG = {
'host': 'db.production.example.com',
'user': 'dbadmin',
'password': 'ProductionDbPass123!', # Eingebettet!
'database': 'production_db'
}
class VulnerableAuth:
# Anfällig: Master-Passwort eingebettet
MASTER_PASSWORD = "MasterSecretKey2024!"
def authenticate(self, password):
return password == self.MASTER_PASSWORD
Korrigierter Code (C/Java)
// Korrigiert: Externe Anmeldedaten-Abfrage in C
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Korrigiert: Anmeldedaten aus Umgebung oder sicherem Speicher abgerufen
char* get_admin_password() {
// Korrigiert: Aus Umgebungsvariable lesen
char *password = getenv("ADMIN_PASSWORD");
if (password == NULL) {
fprintf(stderr, "ADMIN_PASSWORD nicht gesetzt\n");
exit(1);
}
return password;
}
// Korrigiert: Schlüssel zur Laufzeit aus sicherem Speicher abgerufen
int get_encryption_key(unsigned char *key, size_t key_len) {
// Option 1: Aus sicherer Datei mit eingeschränkten Berechtigungen lesen
FILE *keyfile = fopen("/etc/myapp/secrets/encryption.key", "rb");
if (keyfile == NULL) {
return -1;
}
// Datei sollte chmod 0600 sein, im Besitz des App-Benutzers
size_t read = fread(key, 1, key_len, keyfile);
fclose(keyfile);
return (read == key_len) ? 0 : -1;
}
// Korrigiert: API-Schlüssel aus Umgebung
char* get_api_key() {
return getenv("API_KEY");
}
// Korrigiert: Datenbankverbindung aus sicherer Konfiguration
char* get_db_connection() {
// Aus verschlüsselter Konfigurationsdatei oder Secrets Manager lesen
return read_encrypted_config("database.connection");
}
int authenticate(const char *password) {
// Korrigiert: Passwort aus sicherer Quelle holen
char *stored_password = get_admin_password();
// Korrigiert: Zeitkonstanter Vergleich
int result = constant_time_compare(password, stored_password);
return result;
}
void encrypt_data(unsigned char *data, size_t len) {
unsigned char key[16];
// Korrigiert: Schlüssel zur Laufzeit abrufen
if (get_encryption_key(key, sizeof(key)) != 0) {
fprintf(stderr, "Fehler beim Abrufen des Verschlüsselungsschlüssels\n");
return;
}
aes_encrypt(data, len, key);
// Korrigiert: Schlüssel nach Verwendung aus Speicher löschen
explicit_bzero(key, sizeof(key));
}
// Korrigiert: Externe Anmeldedatenverwaltung in Java
import java.sql.*;
import java.io.*;
import java.nio.file.*;
public class SecureConfig {
// Korrigiert: Keine eingebetteten Anmeldedaten!
// Korrigiert: Passwort aus Umgebung abrufen
public static String getAdminPassword() {
String password = System.getenv("ADMIN_PASSWORD");
if (password == null || password.isEmpty()) {
throw new SecurityException("ADMIN_PASSWORD nicht konfiguriert");
}
return password;
}
// Korrigiert: API-Schlüssel aus Secrets Manager abrufen
public static String getApiKey() {
// Verwendung von AWS Secrets Manager, HashiCorp Vault oder ähnlichem
SecretsClient client = SecretsClient.create();
return client.getSecretValue("myapp/stripe_api_key");
}
// Korrigiert: Datenbank-Anmeldedaten aus sicherer Quelle
public static Connection getConnection() throws SQLException {
// Korrigiert: Aus verschlüsselter Konfiguration oder Secrets Manager lesen
String dbUrl = System.getenv("DB_URL");
String dbUser = System.getenv("DB_USER");
String dbPassword = getSecretFromVault("database/password");
if (dbPassword == null) {
throw new SecurityException("Datenbank-Anmeldedaten nicht verfügbar");
}
return DriverManager.getConnection(dbUrl, dbUser, dbPassword);
}
// Korrigiert: Verschlüsselungsschlüssel aus sicherem Schlüsselspeicher
public static byte[] getEncryptionKey() {
try {
// Aus sicherer Datei mit eingeschränkten Berechtigungen lesen
Path keyPath = Paths.get("/etc/myapp/secrets/encryption.key");
// Dateiberechtigungen prüfen (Unix)
// Sollte nur vom Anwendungsbenutzer lesbar sein
return Files.readAllBytes(keyPath);
} catch (IOException e) {
throw new SecurityException("Verschlüsselungsschlüssel kann nicht abgerufen werden", e);
}
}
// Korrigiert: Hash-basierte Authentifizierung ohne eingebetteten Hash
public boolean authenticate(String password) {
// Korrigiert: Gespeicherten Hash aus sicherem Speicher abrufen
String storedHash = getStoredPasswordHash();
// Korrigiert: Sichere Passwortverifizierung verwenden
return BCrypt.checkpw(password, storedHash);
}
private String getStoredPasswordHash() {
// Aus sicherer Datenbank oder Konfiguration abrufen
return secureConfig.get("auth.password_hash");
}
private static String getSecretFromVault(String secretPath) {
// Implementierung mit HashiCorp Vault, AWS Secrets Manager, etc.
VaultClient vault = VaultClient.create();
return vault.readSecret(secretPath);
}
}
# Korrigiert: Externe Anmeldedaten-Abfrage in Python
import os
from functools import lru_cache
import boto3 # Für AWS Secrets Manager
class SecureConfig:
"""Konfigurationsmanager mit externer Secret-Abfrage."""
# Korrigiert: Keine eingebetteten Anmeldedaten!
@staticmethod
def get_admin_password():
"""Admin-Passwort aus Umgebung abrufen."""
password = os.environ.get('ADMIN_PASSWORD')
if not password:
raise ValueError("ADMIN_PASSWORD Umgebungsvariable nicht gesetzt")
return password
@staticmethod
@lru_cache(maxsize=1) # Cache zur Vermeidung wiederholter Aufrufe
def get_aws_credentials():
"""AWS-Anmeldedaten von IAM-Rolle oder Umgebung holen."""
# IAM-Rollen verwenden, wenn in AWS ausgeführt
# Fällt auf Umgebungsvariablen zurück
session = boto3.Session()
credentials = session.get_credentials()
return credentials
@staticmethod
def get_api_key(key_name):
"""API-Schlüssel aus Secrets Manager abrufen."""
client = boto3.client('secretsmanager')
response = client.get_secret_value(SecretId=key_name)
return response['SecretString']
@staticmethod
def get_db_config():
"""Datenbankkonfiguration aus sicherer Quelle holen."""
return {
'host': os.environ.get('DB_HOST', 'localhost'),
'user': os.environ.get('DB_USER'),
'password': SecureConfig.get_api_key('myapp/db_password'),
'database': os.environ.get('DB_NAME')
}
@staticmethod
def get_encryption_key():
"""Verschlüsselungsschlüssel aus sicherem Speicher abrufen."""
# Aus Datei mit eingeschränkten Berechtigungen lesen
key_path = '/etc/myapp/secrets/encryption.key'
# Dateiberechtigungen prüfen
import stat
mode = os.stat(key_path).st_mode
if mode & (stat.S_IRWXG | stat.S_IRWXO):
raise PermissionError("Schlüsseldatei hat unsichere Berechtigungen")
with open(key_path, 'rb') as f:
return f.read()
# Korrigiert: Authentifizierung ohne eingebettete Geheimnisse
class SecureAuth:
def authenticate(self, password):
stored_hash = self._get_stored_hash()
return bcrypt.checkpw(password.encode(), stored_hash.encode())
def _get_stored_hash(self):
# Aus sicherer Datenbank abrufen
return database.get_password_hash(self.username)
Die Korrektur ruft alle sensiblen Daten aus externen sicheren Quellen ab, anstatt sie im Code einzubetten.
Ausgenutzt in der Praxis
DLL-eingebetteter RSA-Privatschlüssel (Netzwerksoftware, 2005)
CVE-2005-1794 dokumentierte ein Produkt, das einen RSA-Privatschlüssel direkt in einer DLL speicherte, wodurch Angreifer den Server spoofen und Man-in-the-Middle-Angriffe gegen alle Benutzer durchführen könnten.
Ausführbare Administrationspasswörter (Unternehmenssoftware, 2001)
CVE-2001-1527 dokumentierte Administrationspasswörter, die im Klartext innerhalb von ausführbaren Dateien gespeichert waren und alle Bereitstellungen der Software kompromittierten.
Tools zum Testen/Ausnutzen
-
Strings — Extrahiert druckbare Strings aus Binärdateien.
-
IDA Pro — Disassembler für Reverse Engineering von Binärdateien.
-
Ghidra — NSA's Open-Source Reverse Engineering Suite.
-
TruffleHog — Scannt nach Geheimnissen in Binärdateien und Code.
CVE-Beispiele
-
CVE-2005-1794 — RSA-Privatschlüssel in DLL gespeichert.
-
CVE-2001-1527 — Administrationspasswörter im Klartext in ausführbarer Datei.
Referenzen
-
MITRE Corporation. "CWE-318: Cleartext Storage of Sensitive Information in Executable." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/318.html
-
OWASP Foundation. "Cryptographic Storage Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html
-
NIST. "Key Management Guidelines." SP 800-57. https://csrc.nist.gov/publications/detail/sp/800-57-part-1/rev-5/final