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

AuswirkungDetails
ZugriffskontrolleBereich: 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, VertraulichkeitBereich: 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

  1. MITRE Corporation. "CWE-309: Use of Password System for Primary Authentication." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/309.html

  2. NIST. "Digital Identity Guidelines." SP 800-63B. https://pages.nist.gov/800-63-3/sp800-63b.html

  3. OWASP Foundation. "Password Storage Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html