Verwendung von Passwortsystem für primäre Authentifizierung
Beschreibung
Verwendung von Passwortsystem für primäre Authentifizierung ist eine Schwachstelle, die auftritt, wenn passwortbasierte Authentifizierung als einziges oder primäres Authentifizierungsmittel dient. Obwohl Passwörter aufgrund ihrer Einfachheit der häufigste Authentifizierungsmechanismus sind, unterliegen sie zahlreichen bekannten Angriffen und Schwächen, die ihre Wirksamkeit reduzieren. Wörterbuchangriffe, Brute-Force-Versuche, Credential Stuffing, Phishing, Keylogging und Social Engineering zielen alle auf Passwortsysteme ab. Die inhärenten Einschränkungen des menschlichen Gedächtnisses führen zu schwachen Passwortentscheidungen, Passwortwiederverwendung über Dienste hinweg und unsicheren Passwortspeicherpraktiken durch Benutzer.
Risiko
Reine Passwort-Authentifizierungssysteme sind anhaltenden und sich entwickelnden Bedrohungen ausgesetzt, die sich im großen Maßstab als hocheffektiv erwiesen haben. Credential-Stuffing-Angriffe nutzen Milliarden von geleakten Benutzername/Passwort-Paaren, um Konten über Dienste hinweg zu kompromittieren. Phishing-Kampagnen ernten erfolgreich Anmeldedaten von Benutzern, unabhängig von der Passwortkomplexität. Rainbow-Table- und Wörterbuchangriffe können schwache Passwörter schnell knacken, während GPU-beschleunigtes Brute-Force auch mäßig komplexe Passwörter anfällig macht. Die menschlichen Faktoren sind gleichermaßen problematisch - Benutzer erstellen vorhersehbare Passwörter, verwenden sie umfangreich wieder und sind anfällig für Social Engineering. Passwortdatenbanken exponieren bei Lecks Anmeldedaten, die über viele Dienste für Jahre gültig bleiben können. Diese kombinierten Risiken machen reine Passwortsysteme grundlegend ungeeignet für den Schutz sensibler Ressourcen.
Lösung
Implementieren Sie robuste Passwortsicherheitskontrollen kombiniert mit zusätzlichen Authentifizierungsmechanismen. Speichern Sie Passwörter mit modernen adaptiven Hashing-Algorithmen (bcrypt, argon2, scrypt) mit eindeutigen Salts pro Passwort. Setzen Sie intelligente Passwortrichtlinien durch, die Länge über Komplexität fördern und gegen bekannte kompromittierte Passwortlisten prüfen. Implementieren Sie Passwortalterung mit vernünftigen Intervallen, um Gültigkeitsfenster für Anmeldedaten zu begrenzen. Erwägen Sie Zero-Knowledge-Passwortprotokolle wie SRP, die Passwörter niemals über das Netzwerk exponieren. Setzen Sie Multi-Faktor-Authentifizierung ein, um unabhängige Verifikationsfaktoren über Passwörter hinaus hinzuzufügen. Schulen Sie Benutzer über Passwortsicherheit, Phishing-Erkennung und die Wichtigkeit einzigartiger Passwörter. Implementieren Sie Kontosperrung und Überwachung, um Passwortangriffe zu erkennen und darauf zu reagieren. Erwägen Sie passwortlose Authentifizierungsalternativen mit Hardware-Token, Biometrie oder kryptografischen Schlüsseln.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Zugriffskontrolle | Bereich: Zugriffskontrolle Angreifer, die Passwörter kompromittieren, werden als gültige Benutzer autorisiert und erhalten vollen Zugriff auf Konten und ihre zugehörigen Privilegien ohne zusätzliche Verifikationsanforderungen. |
| Integrität, Vertraulichkeit | Bereich: Integrität, Vertraulichkeit Mit Kontozugriff können Angreifer sensible Daten lesen, unbefugte Transaktionen durchführen, Kontoeinstellungen ändern und potenziell das kompromittierte Konto nutzen, um andere Systeme oder Benutzer anzugreifen. |
Beispielcode
Anfälliger Code (C)
Die folgenden Beispiele demonstrieren schwache Passwort-Authentifizierungssysteme:
// Anfällig: Grundlegende Passwort-Authentifizierung mit mehreren Schwächen
#include <stdio.h>
#include <string.h>
#include <openssl/sha.h>
typedef struct {
char username[64];
char password_hash[65]; // SHA-256 hex
// Kein Salt gespeichert!
} UserRecord;
// Anfällig: Schwache Hash-Speicherung ohne Salt
int vulnerable_store_password(const char *username, const char *password) {
UserRecord record;
strncpy(record.username, username, sizeof(record.username) - 1);
// Anfällig: SHA-256 ohne Salt - Rainbow-Table-anfällig
unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256((unsigned char*)password, strlen(password), hash);
for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
sprintf(&record.password_hash[i*2], "%02x", hash[i]);
}
return save_user_record(&record);
}
// Anfällig: Kein Schutz gegen Brute Force
int vulnerable_verify_password(const char *username, const char *password) {
UserRecord *record = lookup_user(username);
if (record == NULL) return 0;
// Anfällig: Gleicher schwacher Hash für Vergleich
unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256((unsigned char*)password, strlen(password), hash);
char input_hash[65];
for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
sprintf(&input_hash[i*2], "%02x", hash[i]);
}
// Anfällig: Direkter String-Vergleich (Timing-Angriff)
return strcmp(input_hash, record->password_hash) == 0;
}
// Anfällig: Keine Passwortrichtliniendurchsetzung
int vulnerable_set_password(const char *username, const char *new_password) {
// Akzeptiert jedes Passwort einschließlich:
// - Leere Passwörter
// - Ein-Zeichen-Passwörter
// - Häufige Passwörter wie "password123"
// - Zuvor geleakte Passwörter
return vulnerable_store_password(username, new_password);
}
# Anfällig: Python-Passwortsystem mit häufigen Schwächen
import hashlib
from flask import Flask, request, session
app = Flask(__name__)
# Anfällig: In-Memory-Benutzerspeicher mit schwachem Hashing
users = {
'admin': hashlib.md5('admin123'.encode()).hexdigest(), # MD5 ist unsicher!
}
@app.route('/login', methods=['POST'])
def login():
username = request.form.get('username')
password = request.form.get('password')
# Anfällig: MD5-Hash ohne Salt
password_hash = hashlib.md5(password.encode()).hexdigest()
# Anfällig: Verrät ob Benutzername existiert
if username not in users:
return "Benutzer nicht gefunden", 401
if users[username] == password_hash:
session['user'] = username
return "Anmeldung erfolgreich"
return "Ungültiges Passwort", 401
@app.route('/register', methods=['POST'])
def register():
username = request.form.get('username')
password = request.form.get('password')
# Anfällig: Keine Passwortstärke-Anforderungen
# Akzeptiert "a", "123", "password", etc.
# Anfällig: MD5-Speicherung
users[username] = hashlib.md5(password.encode()).hexdigest()
return "Benutzer erstellt"
@app.route('/change-password', methods=['POST'])
def change_password():
new_password = request.form.get('new_password')
# Anfällig: Keine Verifikation des alten Passworts
# Anfällig: Keine Passworthistorie-Prüfung
# Anfällig: Keine Mindestalter-Anforderung
username = session.get('user')
if username:
users[username] = hashlib.md5(new_password.encode()).hexdigest()
return "Passwort geändert"
return "Nicht angemeldet", 401
// Anfällig: Java-Passwort-Authentifizierung mit Schwächen
import java.security.MessageDigest;
import java.util.HashMap;
public class VulnerablePasswordAuth {
// Anfällig: Statischer Passwortspeicher
private static HashMap<String, String> users = new HashMap<>();
// Anfällig: SHA-1 ist veraltet
public String hashPassword(String password) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] hash = md.digest(password.getBytes("UTF-8"));
StringBuilder hexString = new StringBuilder();
for (byte b : hash) {
hexString.append(String.format("%02x", b));
}
return hexString.toString();
} catch (Exception e) {
return null;
}
}
// Anfällig: Keine Passwortrichtlinie
public boolean registerUser(String username, String password) {
// Akzeptiert jedes Passwort
String hash = hashPassword(password);
users.put(username, hash);
return true;
}
// Anfällig: Mehrere Probleme
public boolean authenticate(String username, String password) {
String storedHash = users.get(username);
// Anfällig: Benutzernamen-Enumeration
if (storedHash == null) {
return false; // Sofortige Rückgabe verrät dass Benutzername nicht existiert
}
String inputHash = hashPassword(password);
// Anfällig: Timing-Angriff durch String-Vergleich
return storedHash.equals(inputHash);
}
}
Korrigierter Code (C)
// Korrigiert: Sicheres Passwort-Authentifizierungssystem
#include <stdio.h>
#include <string.h>
#include <sodium.h> // Verwendung von libsodium für Kryptographie
typedef struct {
char username[64];
char password_hash[crypto_pwhash_STRBYTES]; // Argon2id-Hash
time_t password_created;
time_t password_expires;
int failed_attempts;
} UserRecord;
// Korrigiert: Sicheres Passwort-Hashing mit Argon2id
int secure_store_password(const char *username, const char *password) {
// Korrigiert: Zuerst Passwortstärke validieren
if (!is_password_strong(password)) {
return -1; // Passwort erfüllt Anforderungen nicht
}
// Korrigiert: Gegen bekannte kompromittierte Passwörter prüfen
if (is_password_compromised(password)) {
return -2; // Passwort in Leak-Datenbank gefunden
}
UserRecord record = {0}; // Null-initialisieren
strncpy(record.username, username, sizeof(record.username) - 1);
// Korrigiert: Argon2id mit sicheren Parametern verwenden
// Salt wird automatisch generiert und in Hash eingebettet
if (crypto_pwhash_str(
record.password_hash,
password,
strlen(password),
crypto_pwhash_OPSLIMIT_MODERATE,
crypto_pwhash_MEMLIMIT_MODERATE) != 0) {
return -3; // Nicht genug Speicher
}
record.password_created = time(NULL);
record.password_expires = time(NULL) + (90 * 24 * 60 * 60); // 90 Tage
record.failed_attempts = 0;
return save_user_record(&record);
}
// Korrigiert: Sichere Verifikation mit Schutzmaßnahmen
int secure_verify_password(const char *username, const char *password) {
UserRecord *record = lookup_user(username);
// Korrigiert: Zeitkonstant auch für nicht existierende Benutzer
if (record == NULL) {
// Dummy-Verifikation durchführen um Timing beizubehalten
char dummy_hash[crypto_pwhash_STRBYTES];
crypto_pwhash_str(dummy_hash, password, strlen(password),
crypto_pwhash_OPSLIMIT_MODERATE,
crypto_pwhash_MEMLIMIT_MODERATE);
return 0;
}
// Korrigiert: Auf Kontosperrung prüfen
if (record->failed_attempts >= MAX_FAILED_ATTEMPTS) {
if (time(NULL) - record->last_attempt < LOCKOUT_DURATION) {
return -1; // Konto gesperrt
}
record->failed_attempts = 0; // Nach Sperrzeit zurücksetzen
}
// Korrigiert: Passwortablauf prüfen
if (time(NULL) > record->password_expires) {
return -2; // Passwort abgelaufen
}
// Korrigiert: Argon2id-Verifikation (intern zeitkonstant)
if (crypto_pwhash_str_verify(record->password_hash, password,
strlen(password)) == 0) {
record->failed_attempts = 0;
save_user_record(record);
return 1; // Erfolg
}
// Korrigiert: Fehlgeschlagene Versuche verfolgen
record->failed_attempts++;
record->last_attempt = time(NULL);
save_user_record(record);
return 0;
}
// Korrigiert: Passwortrichtliniendurchsetzung
int is_password_strong(const char *password) {
size_t len = strlen(password);
// Mindestlängen-Anforderung
if (len < 12) return 0;
// Zeichenvielfalt prüfen (nicht nur Komplexitätsregeln)
int has_upper = 0, has_lower = 0, has_digit = 0, has_special = 0;
for (size_t i = 0; i < len; i++) {
if (isupper(password[i])) has_upper = 1;
else if (islower(password[i])) has_lower = 1;
else if (isdigit(password[i])) has_digit = 1;
else has_special = 1;
}
// Mindestens 3 Zeichenklassen erforderlich
int classes = has_upper + has_lower + has_digit + has_special;
if (classes < 3) return 0;
// Auf häufige Muster prüfen
if (contains_common_pattern(password)) return 0;
return 1;
}
# Korrigiert: Sicheres Passwort-Authentifizierungssystem
from flask import Flask, request, session
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
import secrets
import time
from zxcvbn import zxcvbn # Passwortstärke-Prüfer
app = Flask(__name__)
ph = PasswordHasher()
# Korrigiert: Ordnungsgemäße Benutzerspeicherung (in Produktion Datenbank verwenden)
users = {}
password_history = {} # Vorherige Passwörter verfolgen
@app.route('/login', methods=['POST'])
def login():
username = request.form.get('username')
password = request.form.get('password')
# Korrigiert: Rate-Limiting
if is_rate_limited(username, request.remote_addr):
return "Zu viele Versuche, bitte warten", 429
# Korrigiert: Zeitkonstantes Verhalten
user = users.get(username)
if user is None:
# Dummy-Hash durchführen um Timing beizubehalten
try:
ph.verify(get_dummy_hash(), password)
except:
pass
record_failed_attempt(username, request.remote_addr)
return "Ungültige Anmeldedaten", 401
# Korrigiert: Kontosperrung prüfen
if user.get('locked_until', 0) > time.time():
return "Konto gesperrt, versuchen Sie es später erneut", 423
try:
# Korrigiert: Argon2id-Verifikation
ph.verify(user['password_hash'], password)
# Korrigiert: Prüfen ob Passwort Rehash braucht (Parameter geändert)
if ph.check_needs_rehash(user['password_hash']):
user['password_hash'] = ph.hash(password)
# Fehlgeschlagene Versuche zurücksetzen
user['failed_attempts'] = 0
# Korrigiert: Passwortablauf prüfen
if time.time() > user.get('password_expires', 0):
session['must_change_password'] = True
session['user'] = username
session['auth_time'] = time.time()
return "Anmeldung erfolgreich"
except VerifyMismatchError:
record_failed_attempt(username, request.remote_addr)
user['failed_attempts'] = user.get('failed_attempts', 0) + 1
if user['failed_attempts'] >= 5:
user['locked_until'] = time.time() + 900 # 15 Min Sperre
return "Ungültige Anmeldedaten", 401
@app.route('/register', methods=['POST'])
def register():
username = request.form.get('username')
password = request.form.get('password')
# Korrigiert: Passwortstärke-Validierung
strength = zxcvbn(password, user_inputs=[username])
if strength['score'] < 3:
return f"Passwort zu schwach: {strength['feedback']['warning']}", 400
# Korrigiert: Gegen kompromittierte Passwörter prüfen
if is_password_pwned(password):
return "Dieses Passwort wurde in einem Datenleck exponiert", 400
# Korrigiert: Mindestlänge
if len(password) < 12:
return "Passwort muss mindestens 12 Zeichen haben", 400
# Korrigiert: Argon2id-Hashing
password_hash = ph.hash(password)
users[username] = {
'password_hash': password_hash,
'created': time.time(),
'password_expires': time.time() + (90 * 24 * 3600), # 90 Tage
'failed_attempts': 0
}
password_history[username] = [password_hash]
return "Benutzer erstellt"
@app.route('/change-password', methods=['POST'])
def change_password():
if 'user' not in session:
return "Nicht angemeldet", 401
username = session['user']
old_password = request.form.get('old_password')
new_password = request.form.get('new_password')
user = users.get(username)
# Korrigiert: Altes Passwort verifizieren
try:
ph.verify(user['password_hash'], old_password)
except VerifyMismatchError:
return "Aktuelles Passwort falsch", 401
# Korrigiert: Prüfen ob Passwort nicht wiederverwendet wird
history = password_history.get(username, [])
for old_hash in history[-5:]: # Letzte 5 Passwörter prüfen
try:
ph.verify(old_hash, new_password)
return "Kann kürzliche Passwörter nicht wiederverwenden", 400
except VerifyMismatchError:
pass
# Korrigiert: Neue Passwortstärke validieren
strength = zxcvbn(new_password, user_inputs=[username])
if strength['score'] < 3:
return f"Passwort zu schwach: {strength['feedback']['warning']}", 400
# Korrigiert: Passwort aktualisieren
new_hash = ph.hash(new_password)
user['password_hash'] = new_hash
user['password_expires'] = time.time() + (90 * 24 * 3600)
# In Historie verfolgen
history.append(new_hash)
password_history[username] = history[-10:] # Letzte 10 behalten
session.pop('must_change_password', None)
return "Passwort erfolgreich geändert"
Die Korrektur implementiert sicheres Passwort-Hashing, Stärkevalidierung, Leak-Prüfung und Ablaufrichtlinien.
Ausgenutzt in der Praxis
Credential-Stuffing-Kampagnen (Fortlaufend)
Massive Credential-Stuffing-Angriffe mit geleakten Anmeldedaten aus Lecks haben Millionen von nur durch Passwörter geschützten Konten bei Finanzdienstleistern, E-Commerce und Social-Media-Plattformen kompromittiert.
Password-Spray-Angriffe (Unternehmen, Fortlaufend)
Password-Spray-Angriffe, die häufige Passwörter gegen viele Konten testen, haben erfolgreich Unternehmensumgebungen kompromittiert, insbesondere solche ohne Sperrrichtlinien oder MFA.
Tools zum Testen/Ausnutzen
-
Hydra — Passwort-Cracking-Tool das zahlreiche Protokolle unterstützt.
-
Hashcat — Fortgeschrittenes Passwort-Recovery-Tool zum Testen der Hash-Stärke.
-
Have I Been Pwned API — Dienst zum Prüfen von Passwörtern gegen bekannte Lecks.
CVE-Beispiele
Passwortsystem-Schwächen tragen typischerweise zu breiteren Authentifizierungsfehlern bei, anstatt eigenständige CVEs zu erhalten. Verwandte Beispiele umfassen:
-
CVE-2019-11358 — Schwaches Passwort-Hashing in Webanwendung.
-
CVE-2020-1472 — Zerologon - Authentifizierungsumgehung durch kryptografische Schwäche.
Referenzen
-
MITRE Corporation. "CWE-309: Use of Password System for Primary Authentication." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/309.html
-
NIST. "Digital Identity Guidelines." SP 800-63B. https://pages.nist.gov/800-63-3/sp800-63b.html
-
OWASP Foundation. "Password Storage Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html