Ungeschützte Speicherung von Anmeldedaten

Beschreibung

Ungeschützte Speicherung von Anmeldedaten tritt auf, wenn Software Authentifizierungsanmeldedaten (Passwörter, API-Schlüssel, Tokens, Zertifikate) an einem Ort oder auf eine Weise speichert, die für unbefugte Akteure zugänglich ist. Dies umfasst das Speichern von Anmeldedaten in Klartextdateien, unverschlüsselten Datenbanken, Anwendungslogs, weltweit lesbaren Konfigurationsdateien, Browser-Local-Storage oder Quellcode-Repositories. Selbst temporäre Speicherung von Anmeldedaten an unsicheren Orten erzeugt Schwachstellenfenster.

Risiko

Ungeschützt gespeicherte Anmeldedaten werden leicht kompromittiert. Konfigurationsdateien und Umgebungsvariablen werden oft durch Path Traversal, LFI oder Fehlkonfiguration offengelegt. Datenbank-Breaches legen Millionen von Klartext- oder schwach verschlüsselten Anmeldedaten offen. Anmeldedaten in Logs sind oft für ein breiteres Publikum zugänglich als Produktionsdaten. Browser-Storage ist für XSS-Angriffe zugänglich. Quellcode-Repositories enthalten häufig versehentlich committete Anmeldedaten. Jede Offenlegung von Anmeldedaten führt direkt zur Kontokompromittierung.

Lösung

Speichern Sie niemals Anmeldedaten im Klartext. Verwenden Sie ordnungsgemäße Anmeldedaten-Verwaltungssysteme (Secrets-Manager, Vaults). Verschlüsseln Sie Anmeldedaten im Ruhezustand mit separat verwalteten Schlüsseln. Nutzen Sie Betriebssystem-Anmeldedatenspeicher wo verfügbar. Implementieren Sie ordnungsgemäße Zugriffskontrollen auf Anmeldedatenspeicher. Vermeiden Sie das Loggen von Anmeldedaten. Verwenden Sie Umgebungsvariablen nur in Kombination mit ordnungsgemäßer Systemsicherheit. Implementieren Sie Anmeldedaten-Rotation. Verwenden Sie Hardware-Sicherheitsmodule (HSM) für hochwertige Anmeldedaten.

Häufige Auswirkungen

AuswirkungDetails
VertraulichkeitBereich: Anmeldedaten-Diebstahl

Offengelegte Anmeldedaten bieten direkten Zugang zu geschützten Systemen und Daten.
AuthentifizierungBereich: Kontoübernahme

Gestohlene Anmeldedaten ermöglichen Identitätsmissbrauch und unbefugten Zugriff.
IntegritätBereich: Systemkompromittierung

Privilegierte Anmeldedaten führen zur vollständigen Systemkompromittierung.

Beispielcode + Lösungscode

Anfälliger Code

# ANFÄLLIG: Anmeldedaten in Klartext-Konfigurationsdatei
# config.py (ins Repo committet!)
DATABASE_USER = "admin"
DATABASE_PASSWORD = "secretpassword123"
API_KEY = "sk_live_1234567890abcdef"

# ANFÄLLIG: Anmeldedaten in .env-Datei ohne Schutz
# .env (weltweit lesbar)
DB_PASSWORD=supersecret
AWS_SECRET_KEY=abcdefghijklmnop

# ANFÄLLIG: Anmeldedaten in Anwendungslogs
import logging

def authenticate(username, password):
    logging.info(f"Login-Versuch: user={username}, password={password}")  # Loggt Passwort!

    if check_credentials(username, password):
        return create_session(username)
    return None

# ANFÄLLIG: Anmeldedaten ohne Verschlüsselung in Datenbank speichern
def save_api_credentials(user_id, api_key, api_secret):
    db.execute("""
        INSERT INTO api_credentials (user_id, api_key, api_secret)
        VALUES (?, ?, ?)
    """, (user_id, api_key, api_secret))  # Klartextspeicherung!

# ANFÄLLIG: Browser Local Storage
# frontend.js
function saveCredentials(username, password) {
    localStorage.setItem('username', username);
    localStorage.setItem('password', password);  # XSS kann das lesen!
}
// ANFÄLLIG: Anmeldedaten in Properties-Datei
// application.properties (im jar)
database.username=admin
database.password=secretpassword123
api.key=sk_live_1234567890

// ANFÄLLIG: Anmeldedaten in Java-Klasse
public class DatabaseConfig {
    private static final String DB_USER = "admin";
    private static final String DB_PASS = "password123";  // Fest codiert!
}

// ANFÄLLIG: Anmeldedaten in Shared Preferences speichern
public class VulnerablePreferences {
    public void saveCredentials(Context context, String username, String password) {
        SharedPreferences prefs = context.getSharedPreferences("auth", Context.MODE_PRIVATE);
        prefs.edit()
            .putString("username", username)
            .putString("password", password)  // Klartext auf dem Gerät!
            .apply();
    }
}

// ANFÄLLIG: Anmeldedaten-Logging
public void authenticate(String username, String password) {
    logger.debug("Authentifiziere Benutzer {} mit Passwort {}", username, password);  // Loggt Passwort!
    // ...
}
// ANFÄLLIG: Anmeldedaten in JavaScript
const config = {
    dbPassword: 'secretpassword123',  // Im clientseitigen Code!
    apiKey: 'sk_live_1234567890'
};

// ANFÄLLIG: localStorage für sensible Daten
function login(username, password) {
    // Authentifizieren...
    localStorage.setItem('authToken', token);
    localStorage.setItem('password', password);  // XSS-zugänglich!
}

// ANFÄLLIG: Anmeldedaten in Fehler offenlegen
app.use((err, req, res, next) => {
    console.error('Fehler:', {
        error: err,
        config: {
            dbPassword: process.env.DB_PASSWORD,  // Geloggt!
            apiKey: process.env.API_KEY
        }
    });
    res.status(500).send('Fehler');
});

Korrigierter Code

# SICHER: Secrets-Manager verwenden
import boto3
from functools import lru_cache

@lru_cache(maxsize=1)
def get_database_credentials():
    """Anmeldedaten aus AWS Secrets Manager abrufen."""
    client = boto3.client('secretsmanager')

    response = client.get_secret_value(SecretId='prod/database/credentials')

    import json
    return json.loads(response['SecretString'])

def get_db_connection():
    creds = get_database_credentials()
    return connect(
        host=creds['host'],
        user=creds['username'],
        password=creds['password']
    )

# SICHER: HashiCorp Vault
import hvac

def get_api_key():
    """API-Schlüssel aus Vault abrufen."""
    client = hvac.Client(url=os.environ['VAULT_ADDR'])
    client.token = os.environ['VAULT_TOKEN']

    secret = client.secrets.kv.v2.read_secret_version(path='api/production')
    return secret['data']['data']['api_key']

# SICHER: Verschlüsselte Anmeldedatenspeicherung
from cryptography.fernet import Fernet
import keyring

def store_credentials_encrypted(service, username, password):
    """Anmeldedaten mit OS-Keyring speichern."""
    keyring.set_password(service, username, password)

def get_credentials_encrypted(service, username):
    """Anmeldedaten aus OS-Keyring abrufen."""
    return keyring.get_password(service, username)

# SICHER: Ordnungsgemäßes Logging ohne Anmeldedaten
import logging

def authenticate(username, password):
    logging.info(f"Login-Versuch für Benutzer: {username}")  # Kein Passwort!

    if check_credentials(username, password):
        logging.info(f"Login erfolgreich für Benutzer: {username}")
        return create_session(username)
    else:
        logging.warning(f"Login fehlgeschlagen für Benutzer: {username}")
        return None

# SICHER: Verschlüsselte Speicherung in Datenbank
from cryptography.fernet import Fernet

# Schlüssel sollte aus KMS oder Umgebung kommen
ENCRYPTION_KEY = os.environ['CREDENTIAL_ENCRYPTION_KEY']

def save_api_credentials_safe(user_id, api_key, api_secret):
    cipher = Fernet(ENCRYPTION_KEY)

    encrypted_key = cipher.encrypt(api_key.encode())
    encrypted_secret = cipher.encrypt(api_secret.encode())

    db.execute("""
        INSERT INTO api_credentials (user_id, api_key, api_secret)
        VALUES (?, ?, ?)
    """, (user_id, encrypted_key, encrypted_secret))

def get_api_credentials_safe(user_id):
    cipher = Fernet(ENCRYPTION_KEY)

    row = db.execute("""
        SELECT api_key, api_secret FROM api_credentials WHERE user_id = ?
    """, (user_id,)).fetchone()

    return {
        'api_key': cipher.decrypt(row[0]).decode(),
        'api_secret': cipher.decrypt(row[1]).decode()
    }
// SICHER: Secrets-Manager in Java verwenden
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest;

public class SecureCredentials {

    private final SecretsManagerClient secretsClient;

    public SecureCredentials() {
        this.secretsClient = SecretsManagerClient.create();
    }

    public DatabaseCredentials getDatabaseCredentials() {
        GetSecretValueRequest request = GetSecretValueRequest.builder()
            .secretId("prod/database/credentials")
            .build();

        String secretString = secretsClient.getSecretValue(request).secretString();
        return new ObjectMapper().readValue(secretString, DatabaseCredentials.class);
    }
}

// SICHER: Android verschlüsselte Speicherung
import androidx.security.crypto.EncryptedSharedPreferences;
import androidx.security.crypto.MasterKey;

public class SecurePreferences {

    private SharedPreferences encryptedPrefs;

    public SecurePreferences(Context context) throws Exception {
        MasterKey masterKey = new MasterKey.Builder(context)
            .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
            .build();

        encryptedPrefs = EncryptedSharedPreferences.create(
            context,
            "secure_prefs",
            masterKey,
            EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
            EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
        );
    }

    public void saveToken(String token) {
        encryptedPrefs.edit().putString("auth_token", token).apply();
    }

    public String getToken() {
        return encryptedPrefs.getString("auth_token", null);
    }
}

// SICHER: Anmeldedaten-Logging-Bereinigung
public void authenticate(String username, String password) {
    // Niemals Passwörter loggen!
    logger.info("Authentifiziere Benutzer: {}", username);

    try {
        User user = authService.authenticate(username, password);
        logger.info("Authentifizierung erfolgreich für Benutzer: {}", username);
    } catch (AuthenticationException e) {
        logger.warn("Authentifizierung fehlgeschlagen für Benutzer: {}", username);
        throw e;
    }
}

// SICHER: Spring Cloud Config Verschlüsselung
// application.yml
/*
spring:
  datasource:
    password: '{cipher}AgBK1C2g...'  # Verschlüsselt durch Spring Cloud Config Server
*/
// SICHER: Node.js Secrets-Management
const { SecretsManagerClient, GetSecretValueCommand } = require('@aws-sdk/client-secrets-manager');

const client = new SecretsManagerClient({ region: 'us-east-1' });

async function getDbPassword() {
    const command = new GetSecretValueCommand({
        SecretId: 'prod/database/credentials'
    });

    const response = await client.send(command);
    const secret = JSON.parse(response.SecretString);
    return secret.password;
}

// SICHER: Umgebungsvariablen mit dotenv (nicht committet)
// .env.example (committet - nur Vorlage)
/*
DB_PASSWORD=
API_KEY=
JWT_SECRET=
*/

// .env (NICHT committet, in .gitignore)
// DB_PASSWORD=actual_password

require('dotenv').config();

const dbPassword = process.env.DB_PASSWORD;
if (!dbPassword) {
    throw new Error('DB_PASSWORD Umgebungsvariable erforderlich');
}

// SICHER: Browser-Speicherung mit Verschlüsselung
const CryptoJS = require('crypto-js');

class SecureStorage {
    constructor(encryptionKey) {
        this.key = encryptionKey;  // Sollte aus sicherer Quelle kommen
    }

    setItem(name, value) {
        const encrypted = CryptoJS.AES.encrypt(
            JSON.stringify(value),
            this.key
        ).toString();

        sessionStorage.setItem(name, encrypted);  // sessionStorage verwenden, nicht localStorage
    }

    getItem(name) {
        const encrypted = sessionStorage.getItem(name);
        if (!encrypted) return null;

        const decrypted = CryptoJS.AES.decrypt(encrypted, this.key);
        return JSON.parse(decrypted.toString(CryptoJS.enc.Utf8));
    }
}

// SICHER: Token-Speicherung (Passwörter vermeiden)
// Nur Tokens speichern, niemals Passwörter im Browser
function handleLogin(token) {
    // httpOnly Cookies vom Server setzen lassen verwenden!
    // Falls JS notwendig:
    sessionStorage.setItem('token', token);  // Nur Session, nicht persistent

    // Besser: Server httpOnly Cookie setzen lassen
}

// SICHER: Bereinigtes Fehler-Logging
app.use((err, req, res, next) => {
    // Fehler ohne sensible Daten loggen
    const sanitizedError = {
        message: err.message,
        path: req.path,
        method: req.method,
        timestamp: new Date().toISOString()
        // Keine Anmeldedaten oder Konfiguration!
    };

    console.error('Fehler:', sanitizedError);
    res.status(500).json({ error: 'Interner Serverfehler' });
});

Ausgenutzt in der Praxis

GitHub Anmeldedaten-Offenlegung (Fortlaufend)

Millionen von Anmeldedaten wurden in öffentlichen GitHub-Repositories gefunden, was zu AWS-Kontokompromittierungen, Datenbank-Breaches und Systemübernahmen führte.

Über 2016 Breach

Angreifer fanden AWS-Anmeldedaten in einem privaten GitHub-Repository, was zum Zugriff auf 57 Millionen Benutzerdatensätze führte.

Docker Hub Anmeldedaten-Offenlegung

API-Tokens, die in öffentlichen Docker-Images gespeichert waren, wurden verwendet, um Container-Registries zu kompromittieren.


Tools zum Testen/Ausnutzen


CVE-Beispiele


Referenzen

  1. MITRE. "CWE-256: Unprotected Storage of Credentials." https://cwe.mitre.org/data/definitions/256.html

  2. OWASP. "Credential Management Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Credential_Management_Cheat_Sheet.html