Verwendung von Client-seitiger Authentifizierung

Beschreibung

Verwendung von Client-seitiger Authentifizierung tritt auf, wenn eine Anwendung Authentifizierung oder Credential-Verifizierung primär oder ausschließlich auf der Client-Seite durchführt. Dies umfasst das Speichern des Authentifizierungsstatus an Client-zugänglichen Orten (localStorage, Cookies ohne httpOnly, URL-Parameter), das Durchführen von Passwortvergleichen im Client-seitigen Code oder das Erlauben von Client-bestimmtem Authentifizierungsstatus zur Zugriffskontrolle. Angreifer können Client-seitige Authentifizierung manipulieren, um unbefugten Zugriff zu erlangen.

Risiko

Client-seitige Authentifizierung bietet keine echte Sicherheit. Angreifer können Authentifizierungs-Flags im Browser-Speicher setzen, JavaScript modifizieren, um Authentifizierungsprüfungen zu überspringen, oder geschützte APIs direkt aufrufen. Client-seitig gespeicherte Credentials können durch XSS exponiert werden. Authentifizierungstokens ohne Server-Validierung können gefälscht werden. Dies ist eine der schwerwiegendsten Sicherheitsschwächen, da sie im Wesentlichen bedeutet, dass die Anwendung überhaupt keine Authentifizierung hat.

Lösung

Führen Sie Authentifizierung immer auf dem Server durch. Verwenden Sie Server-generierte, kryptographisch sichere Session-Tokens. Speichern Sie Sessions serverseitig oder verwenden Sie signierte Tokens (JWT), die der Server validiert. Vertrauen Sie niemals Client-gemeldetem Authentifizierungsstatus. Implementieren Sie ordnungsgemäßes Session-Management mit httpOnly, Secure Cookies. Validieren Sie Authentifizierung bei jeder geschützten Anfrage. Erwägen Sie Multi-Faktor-Authentifizierung für sensible Operationen.

Häufige Auswirkungen

AuswirkungDetails
AuthentifizierungBereich: Vollständiger Bypass

Angreifer können sich als jeder Benutzer authentifizieren.
AutorisierungBereich: Privilege-Escalation

Zugriffskontrollen basierend auf Client-Daten scheitern.
VertraulichkeitBereich: Daten-Exposure

Alle geschützten Daten werden zugänglich.

Beispielcode + Lösungscode

Verwundbarer Code

// VERWUNDBAR: Authentifizierungsstatus in localStorage
function login(username, password) {
    // Vorgeblich auf Server validieren
    fetch('/api/login', {
        method: 'POST',
        body: JSON.stringify({ username, password })
    });

    // Auth-Status Client-seitig speichern - Angreifer kann modifizieren!
    localStorage.setItem('isLoggedIn', 'true');
    localStorage.setItem('username', username);
    localStorage.setItem('role', 'user');

    // Angreifer in Konsole: localStorage.setItem('role', 'admin')
}

function checkAuth() {
    // Sicherheit komplett Client-seitig!
    if (localStorage.getItem('isLoggedIn') === 'true') {
        return true;
    }
    return false;
}

function requireAdmin() {
    // Angreifer: localStorage.setItem('role', 'admin')
    if (localStorage.getItem('role') === 'admin') {
        showAdminPanel();
    }
}

// VERWUNDBAR: Passwortvergleich in JavaScript
async function authenticateVulnerable(username, password) {
    // Benutzerdaten einschließlich Passwort holen!
    const response = await fetch(`/api/users/${username}`);
    const user = await response.json();

    // Passwort Client-seitig vergleichen - exponiert in Netzwerk/Speicher
    if (user.password === password) {
        sessionStorage.setItem('authenticated', 'true');
        return true;
    }
    return false;
}

// VERWUNDBAR: URL-basierte Authentifizierung
function checkUrlAuth() {
    const params = new URLSearchParams(window.location.search);

    // Angreifer: ?authenticated=true&admin=true
    if (params.get('authenticated') === 'true') {
        if (params.get('admin') === 'true') {
            showAdminDashboard();
        } else {
            showUserDashboard();
        }
    }
}

// VERWUNDBAR: Cookie ohne httpOnly durch JS gesetzt
function setAuthCookie(token) {
    // Kann durch XSS gestohlen werden!
    document.cookie = `auth_token=${token}; path=/`;
}
<!-- VERWUNDBAR: Verstecktes Formularfeld für Authentifizierung -->
<form action="/dashboard" method="POST">
    <input type="hidden" name="authenticated" value="true" />
    <input type="hidden" name="userId" value="12345" />
    <input type="hidden" name="role" value="admin" />
    <!-- Angreifer modifiziert diese mit Dev-Tools -->
    <button type="submit">Dashboard aufrufen</button>
</form>

<!-- VERWUNDBAR: JavaScript Auth-Gate -->
<script>
function protectedPage() {
    // Diese Prüfung ist bedeutungslos!
    if (!window.isAuthenticated) {
        window.location.href = '/login';
        return;  // Angreifer: window.isAuthenticated = true
    }

    loadSensitiveData();
}
</script>
// VERWUNDBAR: Client-Authentifizierungs-Headern vertrauen
@WebServlet("/api/data")
public class VulnerableApiServlet extends HttpServlet {

    protected void doGet(HttpServletRequest request,
                         HttpServletResponse response)
            throws ServletException, IOException {

        // Vertraut Client-bereitgestellter Authentifizierung!
        String authenticated = request.getHeader("X-Authenticated");
        String userId = request.getHeader("X-User-Id");

        // Angreifer fügt Header zu jeder Anfrage hinzu
        if ("true".equals(authenticated)) {
            serveData(response, userId);
        } else {
            response.sendError(401);
        }
    }
}

// VERWUNDBAR: Client-seitige Session-ID-Validierung
@WebServlet("/dashboard")
public class VulnerableDashboardServlet extends HttpServlet {

    protected void doGet(HttpServletRequest request,
                         HttpServletResponse response)
            throws ServletException, IOException {

        // Session-ID aus Client-Parameter holen
        String sessionId = request.getParameter("sessionId");

        // Keine serverseitige Validierung der Session!
        if (sessionId != null && !sessionId.isEmpty()) {
            // Angreifer: ?sessionId=anything
            serveDashboard(response);
        }
    }
}
# VERWUNDBAR: Flask mit Client-vertrautem Auth
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/api/sensitive')
def get_sensitive_vulnerable():
    # Vertraut Client-bereitgestellter Benutzer-ID!
    user_id = request.headers.get('X-User-ID')
    is_admin = request.headers.get('X-Is-Admin') == 'true'

    # Angreifer setzt beliebige Header
    if is_admin:
        return jsonify(get_all_data())
    elif user_id:
        return jsonify(get_user_data(user_id))

    return jsonify({'error': 'Nicht authentifiziert'}), 401

Lösungscode

// SICHER: Server-seitige Authentifizierung mit sicheren Tokens
async function login(username, password) {
    const response = await fetch('/api/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ username, password }),
        credentials: 'include'  // Cookies einschließen
    });

    if (response.ok) {
        // Server setzt httpOnly, Secure Cookie
        // Client behandelt Token nicht direkt
        return true;
    }
    return false;
}

// SICHER: Authentifizierung mit Server bei jeder Prüfung verifizieren
async function checkAuth() {
    try {
        const response = await fetch('/api/verify', {
            credentials: 'include'
        });

        if (response.ok) {
            const user = await response.json();
            return user;  // Server-verifizierte Benutzerdaten
        }
    } catch (e) {
        console.error('Auth-Prüfung fehlgeschlagen');
    }
    return null;
}

// SICHER: Server bestimmt Autorisierung
async function loadDashboard() {
    const response = await fetch('/api/dashboard', {
        credentials: 'include'
    });

    if (response.status === 401) {
        window.location.href = '/login';
        return;
    }

    if (response.status === 403) {
        showAccessDenied();
        return;
    }

    // Server gibt nur Daten zurück, die Benutzer sehen darf
    const data = await response.json();
    displayDashboard(data);
}

// SICHER: Admin-Prüfung ist serverseitig
async function checkAdminAccess() {
    const response = await fetch('/api/admin/verify', {
        credentials: 'include'
    });

    return response.ok;
}
// SICHER: Server-seitiges Session-Management
@WebServlet("/api/login")
public class SafeLoginServlet extends HttpServlet {

    @Inject
    private AuthService authService;

    protected void doPost(HttpServletRequest request,
                          HttpServletResponse response)
            throws ServletException, IOException {

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

        // Credentials serverseitig validieren
        User user = authService.authenticate(username, password);

        if (user != null) {
            // Serverseitige Session erstellen
            HttpSession session = request.getSession(true);
            session.setAttribute("userId", user.getId());
            session.setAttribute("role", user.getRole());

            // Session-ID automatisch in httpOnly-Cookie
            response.setStatus(HttpServletResponse.SC_OK);
            response.getWriter().write("{\"success\": true}");
        } else {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        }
    }
}

// SICHER: Session bei jeder Anfrage verifizieren
@WebServlet("/api/data")
public class SafeDataServlet extends HttpServlet {

    protected void doGet(HttpServletRequest request,
                         HttpServletResponse response)
            throws ServletException, IOException {

        // Session vom Container holen (validiert Cookie)
        HttpSession session = request.getSession(false);

        if (session == null) {
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
            return;
        }

        // Benutzer aus SERVER-SEITIGER Session holen
        Long userId = (Long) session.getAttribute("userId");
        if (userId == null) {
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
            return;
        }

        // Daten für authentifizierten Benutzer bereitstellen
        serveUserData(response, userId);
    }
}

// SICHER: JWT-Validierung auf Server
@WebFilter("/api/*")
public class JwtAuthFilter implements Filter {

    @Inject
    private JwtService jwtService;

    @Override
    public void doFilter(ServletRequest req, ServletResponse res,
                         FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        String authHeader = request.getHeader("Authorization");

        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
            return;
        }

        String token = authHeader.substring(7);

        try {
            // SERVER validiert JWT-Signatur und Claims
            Claims claims = jwtService.validateToken(token);

            // Benutzerinfo an Anfrage anhängen
            request.setAttribute("userId", claims.getSubject());
            request.setAttribute("role", claims.get("role"));

            chain.doFilter(request, response);

        } catch (JwtException e) {
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
        }
    }
}
# SICHER: Flask mit server-seitiger Authentifizierung
from flask import Flask, request, session, jsonify
from functools import wraps
import secrets

app = Flask(__name__)
app.secret_key = secrets.token_hex(32)

# Sichere Session-Cookie konfigurieren
app.config.update(
    SESSION_COOKIE_SECURE=True,
    SESSION_COOKIE_HTTPONLY=True,
    SESSION_COOKIE_SAMESITE='Lax'
)

def require_auth(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        # SERVER-SEITIGE Session prüfen
        if 'user_id' not in session:
            return jsonify({'error': 'Nicht autorisiert'}), 401

        # Benutzer aus Datenbank laden
        user = User.query.get(session['user_id'])
        if not user:
            session.clear()
            return jsonify({'error': 'Ungültige Session'}), 401

        # An Anfrage-Kontext anhängen
        request.current_user = user
        return f(*args, **kwargs)
    return decorated

@app.route('/api/login', methods=['POST'])
def login():
    data = request.json
    username = data.get('username')
    password = data.get('password')

    # SERVER validiert Credentials
    user = User.query.filter_by(username=username).first()

    if user and user.check_password(password):
        # SERVER-SEITIGE Session erstellen
        session['user_id'] = user.id
        session['role'] = user.role

        return jsonify({'success': True})

    return jsonify({'error': 'Ungültige Credentials'}), 401

@app.route('/api/sensitive')
@require_auth
def get_sensitive_safe():
    # user_id kommt aus SERVER-Session
    return jsonify(get_user_data(request.current_user.id))

@app.route('/api/admin/data')
@require_auth
def get_admin_data():
    # Rolle aus SERVER-Session prüfen
    if request.current_user.role != 'admin':
        return jsonify({'error': 'Verboten'}), 403

    return jsonify(get_all_data())

@app.route('/api/verify')
@require_auth
def verify_auth():
    # Endpunkt zur Verifizierung des Authentifizierungsstatus
    return jsonify({
        'authenticated': True,
        'user_id': request.current_user.id,
        'role': request.current_user.role
    })

Ausgenutzt in der Praxis

Single-Page-Application-Bypasses

SPAs mit Client-seitigen Auth-Prüfungen wurden durch Manipulation von localStorage/sessionStorage umgangen.

Mobile-App-Authentifizierungs-Bypass

Mobile Apps, die Auth-Status lokal speichern, wurden durch Modifizierung von App-Daten ausgenutzt.

Versteckte Admin-Panels

Admin-Interfaces, die nur durch JavaScript geschützt waren, wurden entdeckt und aufgerufen.


Tools zum Testen/Ausnutzen

  • Browser-Entwicklertools — Speicher, Cookies modifizieren
  • Burp Suite — Anfragen abfangen und modifizieren
  • Postman — APIs ohne Client aufrufen
  • Mobile-App-Decompiler — Client-seitigen Code analysieren

CVE-Beispiele

  • CVEs durch Authentifizierungs-Bypass in Webanwendungen
  • Mobile-App-Schwachstellen durch Client-seitige Auth

Referenzen

  1. MITRE. "CWE-603: Use of Client-Side Authentication." https://cwe.mitre.org/data/definitions/603.html
  2. OWASP. "Authentication Cheat Sheet." https://cheatsheetseries.owasp.org/