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
| Auswirkung | Details |
|---|---|
| Authentifizierung | Bereich: Vollständiger Bypass Angreifer können sich als jeder Benutzer authentifizieren. |
| Autorisierung | Bereich: Privilege-Escalation Zugriffskontrollen basierend auf Client-Daten scheitern. |
| Vertraulichkeit | Bereich: 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
- MITRE. "CWE-603: Use of Client-Side Authentication." https://cwe.mitre.org/data/definitions/603.html
- OWASP. "Authentication Cheat Sheet." https://cheatsheetseries.owasp.org/