Verwendung von Ein-Faktor-Authentifizierung

Beschreibung

Verwendung von Ein-Faktor-Authentifizierung ist eine Schwachstelle, die auftritt, wenn ein Produkt sich nur auf einen Authentifizierungsfaktor (typischerweise ein Passwort) verlässt, obwohl mehrere Authentifizierungsfaktoren für angemessene Sicherheit erforderlich wären. Authentifizierungsfaktoren werden kategorisiert als: Wissen (Passwörter, PINs), Besitz (Token, Smartcards, Telefone) und Biometrie (Fingerabdrücke, Gesichtserkennung). Während Ein-Faktor-Authentifizierung für Anwendungen mit niedrigem Risiko angemessen sein kann, erfordern sensible Systeme, die Finanzdaten, Gesundheitsinformationen, administrativen Zugriff oder persönliche Daten verarbeiten, mehrere unabhängige Faktoren für angemessenen Schutz gegen Credential-Diebstahl und unbefugten Zugriff.

Risiko

Ein-Faktor-Authentifizierung, insbesondere reine Passwortsysteme, stellt eine erhebliche Sicherheitsschwäche dar, da die Kompromittierung dieses einzelnen Faktors vollständigen Kontozugriff gewährt. Passwörter sind für zahlreiche Angriffsvektoren anfällig: Phishing, Credential Stuffing, Brute Force, Keylogger, Shoulder Surfing und Datenlecks, die Anmeldedatenbanken exponieren. Sobald ein Passwort kompromittiert ist, haben Angreifer uneingeschränkten Zugriff auf das Konto, unabhängig davon, wie stark andere Sicherheitsmaßnahmen sein mögen. Das Risiko wird durch weit verbreitete Passwortwiederverwendung verstärkt - ein Datenleck bei einem Dienst kompromittiert oft Konten bei mehreren Diensten. Hochwertige Ziele wie Administratorkonten, Finanzsysteme und Gesundheitsanwendungen sind besonders gefährdet, da Angreifer diese wegen ihres potenziellen Schadens gezielt angreifen.

Lösung

Implementieren Sie Multi-Faktor-Authentifizierung (MFA) für alle sensiblen Zugriffe und kritischen Funktionen. Fordern Sie mindestens zwei unabhängige Faktoren aus verschiedenen Kategorien - typischerweise Wissen kombiniert mit Besitz. Setzen Sie zeitbasierte Einmalpasswörter (TOTP), Hardware-Sicherheitsschlüssel (FIDO2/WebAuthn), Push-Benachrichtigungen oder SMS-Codes als zweite Faktoren ein, mit Präferenz für Phishing-resistente Methoden wie Hardware-Schlüssel. Wenden Sie risikobasierte Authentifizierung an, die Anforderungen basierend auf Login-Kontext eskaliert (neues Gerät, ungewöhnlicher Standort, sensible Operation). Stellen Sie sicher, dass MFA nicht über einfache Kontoeinstellungen ohne zusätzliche Verifikation deaktiviert werden kann. Erwägen Sie passwortlose Authentifizierung mit Hardware-Token oder Biometrie für Szenarien mit höchsten Sicherheitsanforderungen. Bieten Sie Fallback-Mechanismen, die Sicherheit aufrechterhalten und gleichzeitig Kontowiederherstellung ermöglichen.

Häufige Auswirkungen

AuswirkungDetails
ZugriffskontrolleBereich: Zugriffskontrolle

Wenn das einzelne Authentifizierungsgeheimnis kompromittiert wird, erlangen Angreifer vollen Zugriff auf das Konto. Keine zusätzlichen Barrieren verhindern unbefugten Zugriff, sobald das Passwort bekannt ist.
Integrität, VertraulichkeitBereich: Integrität, Vertraulichkeit

Vollständige Kontokompromittierung ermöglicht Datendiebstahl, unbefugte Transaktionen, Identitätsfälschung und potenzielle laterale Bewegung zu anderen Systemen unter Verwendung gesammelter Anmeldedaten.

Beispielcode

Anfälliger Code (Java)

Die folgenden Beispiele demonstrieren Ein-Faktor-Authentifizierungs-Implementierungen:

// Anfällig: Nur-Passwort-Authentifizierung für sensibles System
import java.security.MessageDigest;
import javax.servlet.http.*;

public class VulnerableBankingAuth extends HttpServlet {

    protected void doPost(HttpServletRequest request,
                          HttpServletResponse response) {

        String username = request.getParameter("username");
        String password = request.getParameter("password");

        // Anfällig: Nur Passwort-Authentifizierung für Banking-System
        if (authenticateUser(username, password)) {
            // Voller Zugriff nur mit einem Passwort gewährt!
            HttpSession session = request.getSession();
            session.setAttribute("authenticated", true);
            session.setAttribute("username", username);

            // Kann jetzt auf sensible Banking-Funktionen zugreifen
            response.sendRedirect("/dashboard");
        } else {
            response.sendRedirect("/login?error=invalid");
        }
    }

    // Anfällig: Schwacher Hash ohne Salt
    private boolean authenticateUser(String username, String password) {
        User user = userRepository.findByUsername(username);
        if (user == null) return false;

        // Ebenfalls anfällig: SHA-1 ist schwach
        String hashedInput = sha1Hash(password);
        return hashedInput.equals(user.getPasswordHash());
    }

    private String sha1Hash(String input) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            byte[] hash = md.digest(input.getBytes());
            return bytesToHex(hash);
        } catch (Exception e) {
            return null;
        }
    }
}
# Anfällig: Admin-Portal mit nur Passwort-Authentifizierung
from flask import Flask, request, session, redirect

app = Flask(__name__)

@app.route('/admin/login', methods=['POST'])
def admin_login():
    username = request.form.get('username')
    password = request.form.get('password')

    # Anfällig: Admin-Zugriff nur mit Passwort
    admin = Admin.query.filter_by(username=username).first()

    if admin and admin.check_password(password):
        # Voller Admin-Zugriff mit nur einem Faktor!
        session['admin_authenticated'] = True
        session['admin_id'] = admin.id
        session['admin_role'] = admin.role  # Könnte Superadmin sein!

        return redirect('/admin/dashboard')

    return redirect('/admin/login?error=1')

# Anfällig: Sensible Operationen ohne zweiten Faktor
@app.route('/admin/users/delete/<user_id>', methods=['POST'])
def delete_user(user_id):
    # Prüft nur Session, keine zusätzliche Verifikation
    if session.get('admin_authenticated'):
        User.query.filter_by(id=user_id).delete()
        db.session.commit()
        return redirect('/admin/users')

    return redirect('/admin/login')
// Anfällig: C-Authentifizierung nur mit Passwort
#include <stdio.h>
#include <string.h>
#include <openssl/sha.h>

typedef struct {
    char username[64];
    char password_hash[65];
    int privilege_level;  // 0=Benutzer, 1=Admin, 2=Superadmin
} UserRecord;

// Anfällig: Gibt vollen Zugriff einschließlich Admin nur mit Passwort zurück
int vulnerable_authenticate(const char *username, const char *password) {
    UserRecord *user = lookup_user(username);

    if (user == NULL) {
        return -1;  // Benutzer nicht gefunden
    }

    char input_hash[65];
    compute_sha256(password, input_hash);

    // Anfällig: Passwort allein gewährt jede Berechtigungsstufe
    if (strcmp(input_hash, user->password_hash) == 0) {
        return user->privilege_level;  // Gibt 2 für Superadmin zurück!
    }

    return -1;  // Authentifizierung fehlgeschlagen
}

int main() {
    char username[64], password[64];

    printf("Benutzername: ");
    scanf("%63s", username);
    printf("Passwort: ");
    scanf("%63s", password);

    int level = vulnerable_authenticate(username, password);

    if (level >= 0) {
        // Anfällig: Voller Zugriff mit nur einem Faktor
        if (level == 2) {
            printf("Willkommen, Superadmin!\n");
            superadmin_menu();  // Volle Systemkontrolle!
        }
    }

    return 0;
}

Korrigierter Code (Java)

// Korrigiert: Multi-Faktor-Authentifizierung für sensibles System
import java.security.SecureRandom;
import javax.servlet.http.*;

public class SecureBankingAuth extends HttpServlet {

    private final TOTPService totpService;
    private final MFAEnrollmentService mfaService;

    protected void doPost(HttpServletRequest request,
                          HttpServletResponse response) {

        String action = request.getParameter("action");

        if ("password".equals(action)) {
            handlePasswordStep(request, response);
        } else if ("mfa".equals(action)) {
            handleMFAStep(request, response);
        }
    }

    // Korrigiert: Passwort ist nur der erste Faktor
    private void handlePasswordStep(HttpServletRequest request,
                                    HttpServletResponse response) {
        String username = request.getParameter("username");
        String password = request.getParameter("password");

        if (authenticatePassword(username, password)) {
            // Korrigiert: Passwort verifiziert, aber noch nicht vollständig authentifiziert
            HttpSession session = request.getSession();
            session.setAttribute("pending_mfa", true);
            session.setAttribute("pending_username", username);
            session.setMaxInactiveInterval(300);  // 5 Min um MFA abzuschließen

            // Korrigiert: Weiterleitung zum MFA-Schritt
            response.sendRedirect("/login/mfa");
        } else {
            response.sendRedirect("/login?error=invalid");
        }
    }

    // Korrigiert: Zweiter Faktor erforderlich
    private void handleMFAStep(HttpServletRequest request,
                               HttpServletResponse response) {
        HttpSession session = request.getSession(false);

        // Verifizieren dass erster Faktor abgeschlossen wurde
        if (session == null || !Boolean.TRUE.equals(session.getAttribute("pending_mfa"))) {
            response.sendRedirect("/login");
            return;
        }

        String username = (String) session.getAttribute("pending_username");
        String totpCode = request.getParameter("totp_code");

        // Korrigiert: TOTP zweiten Faktor verifizieren
        if (totpService.verifyCode(username, totpCode)) {
            // Ausstehenden Zustand löschen
            session.removeAttribute("pending_mfa");
            session.removeAttribute("pending_username");

            // Korrigiert: Erst jetzt ist Benutzer vollständig authentifiziert
            session.setAttribute("authenticated", true);
            session.setAttribute("username", username);
            session.setAttribute("auth_time", System.currentTimeMillis());

            auditService.logSuccessfulMFA(username);
            response.sendRedirect("/dashboard");
        } else {
            auditService.logFailedMFA(username);
            // Nicht verraten welcher Faktor fehlgeschlagen ist
            session.invalidate();
            response.sendRedirect("/login?error=invalid");
        }
    }

    // Korrigiert: Starkes Passwort-Hashing mit Salt
    private boolean authenticatePassword(String username, String password) {
        User user = userRepository.findByUsername(username);

        // Zeitkonstante Prüfung um Enumeration zu verhindern
        if (user == null) {
            passwordEncoder.encode(password);  // Dummy-Operation
            return false;
        }

        // Verwendung von bcrypt/argon2 für Passwortverifikation
        return passwordEncoder.matches(password, user.getPasswordHash());
    }
}

// Korrigiert: TOTP-Verifikationsdienst
@Service
public class TOTPService {

    private static final int TIME_STEP_SECONDS = 30;
    private static final int CODE_DIGITS = 6;

    public boolean verifyCode(String username, String code) {
        User user = userRepository.findByUsername(username);
        if (user == null || user.getTotpSecret() == null) {
            return false;
        }

        // Uhrendrift erlauben (1 Schritt vor und nach)
        long currentTime = System.currentTimeMillis() / 1000;

        for (int i = -1; i <= 1; i++) {
            String expectedCode = generateTOTP(
                user.getTotpSecret(),
                (currentTime / TIME_STEP_SECONDS) + i
            );

            if (MessageDigest.isEqual(code.getBytes(), expectedCode.getBytes())) {
                // Code-Wiederverwendung verhindern
                if (isCodeAlreadyUsed(username, code)) {
                    return false;
                }
                markCodeUsed(username, code);
                return true;
            }
        }

        return false;
    }
}
# Korrigiert: Multi-Faktor-Authentifizierung für Admin-Portal
from flask import Flask, request, session, redirect
import pyotp
from functools import wraps

app = Flask(__name__)

def require_mfa(f):
    """Decorator der abgeschlossene MFA erfordert."""
    @wraps(f)
    def decorated(*args, **kwargs):
        if not session.get('mfa_verified'):
            return redirect('/admin/login')
        return f(*args, **kwargs)
    return decorated

def require_step_up(f):
    """Decorator der Step-Up-Authentifizierung für sensible Ops erfordert."""
    @wraps(f)
    def decorated(*args, **kwargs):
        # Prüfen ob Step-Up-Auth kürzlich abgeschlossen wurde
        step_up_time = session.get('step_up_time', 0)
        if time.time() - step_up_time > 300:  # 5-Minuten-Fenster
            session['pending_action'] = request.url
            return redirect('/admin/step-up')
        return f(*args, **kwargs)
    return decorated

@app.route('/admin/login', methods=['POST'])
def admin_login():
    step = request.form.get('step', 'password')

    if step == 'password':
        username = request.form.get('username')
        password = request.form.get('password')

        admin = Admin.query.filter_by(username=username).first()

        if admin and admin.check_password(password):
            # Korrigiert: Passwort verifiziert, MFA erforderlich
            session['pending_admin'] = admin.id
            session['pending_username'] = username
            return redirect('/admin/mfa')

        return redirect('/admin/login?error=1')

    elif step == 'mfa':
        if 'pending_admin' not in session:
            return redirect('/admin/login')

        totp_code = request.form.get('totp_code')
        admin = Admin.query.get(session['pending_admin'])

        if admin and verify_totp(admin.totp_secret, totp_code):
            # Korrigiert: Beide Faktoren verifiziert
            session.pop('pending_admin')
            session.pop('pending_username')

            session['admin_authenticated'] = True
            session['admin_id'] = admin.id
            session['mfa_verified'] = True
            session['auth_time'] = time.time()

            return redirect('/admin/dashboard')

        return redirect('/admin/mfa?error=1')

# Korrigiert: Sensible Operationen erfordern Step-Up-Authentifizierung
@app.route('/admin/users/delete/<user_id>', methods=['POST'])
@require_mfa
@require_step_up  # Erfordert Re-Authentifizierung
def delete_user(user_id):
    # Zusätzliche Verifikation für destruktive Aktion
    User.query.filter_by(id=user_id).delete()
    db.session.commit()
    audit_log.record('user_deleted', user_id, session['admin_id'])
    return redirect('/admin/users')

def verify_totp(secret, code):
    """TOTP-Code mit Uhrendrift-Toleranz verifizieren."""
    totp = pyotp.TOTP(secret)
    return totp.verify(code, valid_window=1)

Die Korrektur implementiert Multi-Faktor-Authentifizierung, die sowohl Passwort- als auch TOTP-Verifikation erfordert.


Ausgenutzt in der Praxis

MFA-Umgehung in Chat-Anwendung (2022)

CVE-2022-35248 dokumentierte eine Chat-Anwendung, bei der der zweite Faktor der Zwei-Faktor-Authentifizierung übersprungen wurde, wenn Central Authentication Service (CAS) aktiviert war, was die Sicherheit effektiv auf Ein-Faktor reduzierte.

Credential-Stuffing-Angriffe (Fortlaufend)

Zahlreiche hochkarätige Datenlecks haben Ein-Faktor-Authentifizierung ausgenutzt, indem Anmeldedaten aus anderen Datenlecks verwendet wurden, um unbefugten Zugriff auf nur durch Passwörter geschützte Konten zu erlangen.


Tools zum Testen/Ausnutzen


CVE-Beispiele


Referenzen

  1. MITRE Corporation. "CWE-308: Use of Single-factor Authentication." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/308.html

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

  3. OWASP Foundation. "Multifactor Authentication Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Multifactor_Authentication_Cheat_Sheet.html