Schwache Passwortanforderungen
Beschreibung
Schwache Passwortanforderungen ist eine Schwachstelle, bei der ein Produkt nicht verlangt, dass Benutzer starke Passwörter haben, wodurch Angreifer Anmeldedaten leicht erraten können. Authentifizierungssysteme verlassen sich auf gemerkte Geheimnisse (Passwörter), um die Benutzeridentität zu verifizieren, und die Passwortkomplexität muss ausreichend sein, um zu verhindern, dass Angreifer Anmeldedaten durch Brute-Force- oder Wörterbuchangriffe erraten. Schwache Anforderungen wie das Erlauben kurzer Passwörter, kein Erfordernis von Zeichenvielfalt, das Zulassen gängiger Wörterbuchbegriffe oder das Nicht-Blockieren kontextueller Zeichenfolgen machen es Angreifern erheblich einfacher, Benutzerkonten zu kompromittieren.
Risiko
Schwache Passwortanforderungen ermöglichen direkt anmeldedatenbasierte Angriffe. Kurze Passwörter können je nach Länge in Sekunden bis Stunden per Brute-Force geknackt werden. Passwörter ohne Komplexitätsanforderungen sind anfällig für Wörterbuchangriffe mit gängigen Wortlisten. Das Erlauben von Benutzernamen, Anwendungsnamen oder gängigen Phrasen in Passwörtern macht Konten trivial kompromittierbar. Ohne Einschränkungen zur Passwortwiederverwendung sind Credential-Stuffing-Angriffe mit geleakten Datenbanken hochgradig erfolgreich. Schwache Passwörter auf Administratorkonten können zur vollständigen Systemkompromittierung führen. Der kumulative Effekt ist, dass Angreifer unbefugten Zugriff auf Benutzerkonten erlangen und möglicherweise zu systemweiten Breaches eskalieren können.
Lösung
Implementieren Sie umfassende Passwortanforderungen: Erzwingen Sie Mindestlänge (mindestens 8 Zeichen, vorzugsweise 12+), Maximallänge mit Erlaubnis für Passphrasen und blockieren Sie gängige Wörterbuch-Passwörter. Beschränken Sie kontextuelle Zeichenfolgen wie Benutzernamen oder Anwendungsnamen. Erwägen Sie das Erfordernis gemischter Zeichensätze, aber priorisieren Sie Länge über Komplexität für bessere Sicherheit und Benutzerfreundlichkeit. Implementieren Sie Passwortstärke-Anzeigen, um Benutzer zu führen. Verwenden Sie bcrypt, Argon2 oder PBKDF2 für Passwort-Hashing. Fügen Sie Multi-Faktor-Authentifizierung als Defense-in-Depth hinzu. Erwägen Sie Passphrasen-basierte Ansätze, die langere, leichter merkbare Passwörter fördern. Beachten Sie, dass erzwungene periodische Passwortänderungen nicht mehr empfohlen werden, da sie oft zu schwächeren Passwörtern führen.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Zugriffskontrolle | Bereich: Zugriffskontrolle Privilegien erlangen oder Identität annehmen - Ein Angreifer könnte Benutzerpasswörter leicht erraten und unbefugten Zugriff auf Konten erlangen, möglicherweise einschließlich Administratorkonten mit erhöhten Privilegien. |
| Vertraulichkeit | Bereich: Vertraulichkeit Anwendungsdaten lesen - Kompromittierte Konten können Zugriff auf sensible Benutzerdaten, persönliche Informationen oder vertrauliche Geschäftsinformationen bieten. |
Beispielcode
Verwundbarer Code
// Verwundbar: Keine Passwortstärke-Validierung
public class VulnerableAuthService {
// Verwundbar: Keine Mindestlängenanforderung
public boolean registerUser(String username, String password) {
// Verwundbar: Akzeptiert jedes Passwort
if (password != null && !password.isEmpty()) {
User user = new User(username, hashPassword(password));
userRepository.save(user);
return true;
}
return false;
}
// Verwundbar: Schwacher Passwort-Hash
private String hashPassword(String password) {
return DigestUtils.md5Hex(password); // MD5 ist unsicher
}
}
// Verwundbar: Minimale Validierung, die leicht umgangen werden kann
public class WeakPasswordValidator {
// Verwundbar: Prüft nur Länge
public boolean validatePassword(String password) {
return password != null && password.length() >= 4; // Zu kurz!
}
// Verwundbar: Einfache Komplexitätsprüfung leicht erfüllbar
public boolean hasUpperAndLower(String password) {
return password.matches(".*[A-Z].*") &&
password.matches(".*[a-z].*");
// "Aa" würde bestehen - nicht sicher
}
}
# Verwundbar: Python-Registrierung mit schwachen Anforderungen
from flask import Flask, request
import hashlib
app = Flask(__name__)
@app.route('/register', methods=['POST'])
def vulnerable_register():
username = request.form['username']
password = request.form['password']
# Verwundbar: Keine Längenprüfung
# Verwundbar: Keine Komplexitätsanforderungen
# Verwundbar: Keine Prüfung auf gängige Passwörter
# Verwundbar: Schwacher Hash
password_hash = hashlib.md5(password.encode()).hexdigest()
save_user(username, password_hash)
return "Benutzer registriert"
# Verwundbar: Akzeptiert extrem schwache Passwörter
def validate_password_weak(password):
# Verwundbar: Prüft nur auf nicht-leer
return bool(password)
# Verwundbar: Trivial erfüllbare Anforderungen
def validate_password_minimal(password):
# Verwundbar: 6 Zeichen sind zu kurz
if len(password) < 6:
return False
# Keine weiteren Prüfungen - "aaaaaa" würde bestehen
return True
// Verwundbar: Nur clientseitige Validierung
function vulnerableValidatePassword(password) {
// Verwundbar: Keine serverseitige Validierung
// Angreifer kann durch direkten API-Aufruf umgehen
// Verwundbar: Schwache Anforderungen
if (password.length >= 4) {
return true;
}
return false;
}
// Verwundbar: Express.js Registrierungs-Endpunkt
app.post('/register', (req, res) => {
const { username, password } = req.body;
// Verwundbar: Keine Passwortvalidierung
// Verwundbar: Erlaubt "1234", "password", "admin", etc.
const user = createUser(username, password);
res.json({ success: true });
});
Lösungscode
// Behoben: Umfassende Passwortvalidierung
public class SecurePasswordValidator {
private static final int MIN_LENGTH = 12;
private static final int MAX_LENGTH = 128;
private static final Set<String> COMMON_PASSWORDS = loadCommonPasswords();
public ValidationResult validatePassword(String password, String username) {
List<String> errors = new ArrayList<>();
// Behoben: Null/Leer prüfen
if (password == null || password.isEmpty()) {
return ValidationResult.invalid("Passwort ist erforderlich");
}
// Behoben: Mindestlänge erzwingen
if (password.length() < MIN_LENGTH) {
errors.add("Passwort muss mindestens " + MIN_LENGTH + " Zeichen haben");
}
// Behoben: Maximallänge erzwingen (DoS bei Hashing verhindern)
if (password.length() > MAX_LENGTH) {
errors.add("Passwort darf " + MAX_LENGTH + " Zeichen nicht überschreiten");
}
// Behoben: Gängige Passwörter blockieren
if (COMMON_PASSWORDS.contains(password.toLowerCase())) {
errors.add("Dieses Passwort ist zu gebräuchlich");
}
// Behoben: Kontextuelle Zeichenfolgen blockieren
if (username != null && password.toLowerCase()
.contains(username.toLowerCase())) {
errors.add("Passwort darf Ihren Benutzernamen nicht enthalten");
}
// Behoben: Zeichenvielfalt prüfen (optional, Länge bevorzugen)
if (!hasCharacterVariety(password)) {
errors.add("Passwort sollte eine Mischung von Zeichentypen enthalten");
}
// Behoben: Gegen geleakte Passwörter prüfen (optional)
if (isBreachedPassword(password)) {
errors.add("Dieses Passwort ist in Datenlecks aufgetaucht");
}
return errors.isEmpty() ?
ValidationResult.valid() :
ValidationResult.invalid(errors);
}
private boolean hasCharacterVariety(String password) {
boolean hasLower = password.matches(".*[a-z].*");
boolean hasUpper = password.matches(".*[A-Z].*");
boolean hasDigit = password.matches(".*\\d.*");
boolean hasSpecial = password.matches(".*[!@#$%^&*(),.?\":{}|<>].*");
// Mindestens 3 von 4 Zeichentypen erfordern
int count = 0;
if (hasLower) count++;
if (hasUpper) count++;
if (hasDigit) count++;
if (hasSpecial) count++;
return count >= 3;
}
private boolean isBreachedPassword(String password) {
// HaveIBeenPwned k-Anonymity API verwenden
return PwnedPasswordsService.isCompromised(password);
}
private static Set<String> loadCommonPasswords() {
// Top 10000 gängige Passwörter laden
return CommonPasswordLoader.load("/common-passwords.txt");
}
}
// Behoben: Sicheres Passwort-Hashing
public class SecureAuthService {
private static final int BCRYPT_COST = 12;
public boolean registerUser(String username, String password) {
// Behoben: Passwortstärke validieren
ValidationResult result = passwordValidator
.validatePassword(password, username);
if (!result.isValid()) {
throw new WeakPasswordException(result.getErrors());
}
// Behoben: Starkes Passwort-Hashing verwenden
String hash = BCrypt.hashpw(password, BCrypt.gensalt(BCRYPT_COST));
User user = new User(username, hash);
userRepository.save(user);
return true;
}
public boolean verifyPassword(String username, String password) {
User user = userRepository.findByUsername(username);
if (user == null) {
// Behoben: Timing-Angriffe verhindern
BCrypt.hashpw(password, BCrypt.gensalt(BCRYPT_COST));
return false;
}
return BCrypt.checkpw(password, user.getPasswordHash());
}
}
# Behoben: Sichere Passwortvalidierung in Python
import re
import bcrypt
from typing import List, Tuple
import requests
class SecurePasswordValidator:
MIN_LENGTH = 12
MAX_LENGTH = 128
def __init__(self):
self.common_passwords = self._load_common_passwords()
def validate(self, password: str, username: str = None) -> Tuple[bool, List[str]]:
errors = []
if not password:
return False, ["Passwort ist erforderlich"]
# Behoben: Längenanforderungen erzwingen
if len(password) < self.MIN_LENGTH:
errors.append(f"Passwort muss mindestens {self.MIN_LENGTH} Zeichen haben")
if len(password) > self.MAX_LENGTH:
errors.append(f"Passwort darf {self.MAX_LENGTH} Zeichen nicht überschreiten")
# Behoben: Gängige Passwörter blockieren
if password.lower() in self.common_passwords:
errors.append("Dieses Passwort ist zu gebräuchlich")
# Behoben: Benutzername in Passwort blockieren
if username and username.lower() in password.lower():
errors.append("Passwort darf Ihren Benutzernamen nicht enthalten")
# Behoben: Zeichenvielfalt prüfen
if not self._has_variety(password):
errors.append("Passwort sollte gemischte Zeichen enthalten")
# Behoben: Gegen geleakte Passwörter prüfen
if self._is_breached(password):
errors.append("Dieses Passwort ist in Datenlecks aufgetaucht")
return len(errors) == 0, errors
def _has_variety(self, password: str) -> bool:
checks = [
re.search(r'[a-z]', password), # Kleinbuchstaben
re.search(r'[A-Z]', password), # Großbuchstaben
re.search(r'\d', password), # Ziffer
re.search(r'[!@#$%^&*(),.?":{}|<>]', password) # Sonderzeichen
]
return sum(bool(c) for c in checks) >= 3
def _is_breached(self, password: str) -> bool:
# HaveIBeenPwned k-Anonymity API verwenden
import hashlib
sha1 = hashlib.sha1(password.encode()).hexdigest().upper()
prefix, suffix = sha1[:5], sha1[5:]
response = requests.get(f'https://api.pwnedpasswords.com/range/{prefix}')
if response.status_code == 200:
hashes = dict(line.split(':') for line in response.text.splitlines())
return suffix in hashes
return False
def _load_common_passwords(self) -> set:
# Gängige Passwortliste laden
with open('common-passwords.txt', 'r') as f:
return set(line.strip().lower() for line in f)
# Behoben: Sicherer Registrierungs-Endpunkt
from flask import Flask, request, jsonify
app = Flask(__name__)
password_validator = SecurePasswordValidator()
@app.route('/register', methods=['POST'])
def secure_register():
username = request.form.get('username')
password = request.form.get('password')
# Behoben: Serverseitige Validierung
is_valid, errors = password_validator.validate(password, username)
if not is_valid:
return jsonify({'errors': errors}), 400
# Behoben: Starkes Passwort-Hashing
password_hash = bcrypt.hashpw(
password.encode(),
bcrypt.gensalt(rounds=12)
)
save_user(username, password_hash)
return jsonify({'message': 'Benutzer erfolgreich registriert'})
CVE-Beispiele
- CVE-2020-4574: IBM Key Management Server erfordert standardmäßig keine starken Passwörter, was Angreifern ermöglicht, Benutzerkonten durch schwache Anmeldedaten leichter zu kompromittieren.
Referenzen
- MITRE Corporation. "CWE-521: Weak Password Requirements." https://cwe.mitre.org/data/definitions/521.html
- NIST SP 800-63B. "Digital Identity Guidelines: Authentication and Lifecycle Management."
- OWASP. "Authentication Cheat Sheet."