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

AuswirkungDetails
VertraulichkeitUmfang: 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, AuthentifizierungUmfang: 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

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

  2. OWASP Foundation. "Cryptographic Storage Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html

  3. NIST. "Key Management Guidelines." SP 800-57. https://csrc.nist.gov/publications/detail/sp/800-57-part-1/rev-5/final