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
| Auswirkung | Details |
|---|---|
| Vertraulichkeit | Bereich: Anmeldedaten-Diebstahl Offengelegte Anmeldedaten bieten direkten Zugang zu geschützten Systemen und Daten. |
| Authentifizierung | Bereich: Kontoübernahme Gestohlene Anmeldedaten ermöglichen Identitätsmissbrauch und unbefugten Zugriff. |
| Integrität | Bereich: 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
-
TruffleHog — Anmeldedaten in Git-Repos finden.
-
GitLeaks — Nach Secrets scannen.
-
grep/ripgrep — Nach Anmeldedatenmustern suchen.
-
Snaffler — Windows Anmeldedaten-Suche.
CVE-Beispiele
-
CVE-2019-5736 — Container-Anmeldedaten-Offenlegung.
-
CVE-2021-22893 — Pulse Secure Anmeldedatenspeicherung.
-
CVE-2020-5741 — Plex Klartext-Anmeldedatenspeicherung.
Referenzen
-
MITRE. "CWE-256: Unprotected Storage of Credentials." https://cwe.mitre.org/data/definitions/256.html
-
OWASP. "Credential Management Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Credential_Management_Cheat_Sheet.html