Verwendung persistenter Cookies mit sensiblen Informationen
Beschreibung
Verwendung persistenter Cookies mit sensiblen Informationen tritt auf, wenn eine Webanwendung sensible Daten (Sitzungstokens, Anmeldedaten, persönliche Informationen oder sicherheitsrelevante Daten) in Cookies speichert, die über die Browser-Sitzung hinaus bestehen. Persistente Cookies verbleiben auf dem Gerät des Benutzers, bis sie ablaufen oder explizit gelöscht werden. Dies erzeugt erweiterte Expositionsfenster, in denen die sensiblen Daten durch verschiedene Angriffsvektoren gestohlen werden können, einschließlich physischem Zugriff, Malware oder Cookie-Diebstahl-Techniken.
Risiko
Persistente Cookies erweitern das Angriffsfenster für sensible Datenoffenlegung erheblich. Auf gemeinsam genutzten oder öffentlichen Computern können nachfolgende Benutzer auf authentifizierte Sitzungen vorheriger Benutzer zugreifen. Malware kann persistente Cookies von der Festplatte ernten. Wenn Cookies keine ordnungsgemäßen Sicherheitsflags haben, sind sie anfällig für Abfangen und Cross-Site-Angriffe. Sitzungstokens in persistenten Cookies ermöglichen langfristiges Session-Hijacking. Diese Schwachstelle ist besonders gefährlich für Finanz-, Gesundheits- und andere Hochsicherheitsanwendungen, bei denen erweiterte Authentifizierung erhebliche Risiken darstellt.
Lösung
Verwenden Sie Sitzungscookies (ohne Expires/Max-Age) für sensible Daten, damit sie beim Schließen des Browsers gelöscht werden. Wenn Persistenz für Benutzerkomfort erforderlich ist (Remember Me), verwenden Sie ein separates sicheres Token mit eingeschränkten Privilegien anstelle der vollständigen Sitzung. Setzen Sie kurze Ablaufzeiten für persistente Cookies. Verwenden Sie immer Secure- und HttpOnly-Flags. Implementieren Sie SameSite=Strict oder Lax. Speichern Sie nur Referenzen (IDs) in Cookies, sensible Daten serverseitig. Bieten Sie Benutzern Sitzungsverwaltungskontrollen. Invalidieren Sie persistente Tokens serverseitig beim Logout.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Vertraulichkeit | Bereich: Datenoffenlegung Sensible Informationen bleiben auf der Festplatte, wo sie zugänglich sind. |
| Authentifizierung | Bereich: Session-Hijacking Persistente Sitzungstokens ermöglichen langfristigen unbefugten Zugriff. |
| Datenschutz | Bereich: Tracking Persistente Cookies ermöglichen erweitertes Benutzer-Tracking und -Profiling. |
Beispielcode + Lösungscode
Verwundbarer Code
# VERWUNDBAR: Sensible Daten in persistentem Cookie
from flask import Flask, request, make_response
@app.route('/login', methods=['POST'])
def login_vulnerable():
username = request.form['username']
password = request.form['password']
if authenticate(username, password):
response = make_response('Login erfolgreich')
# Sitzungstoken in persistentem Cookie - schlecht!
response.set_cookie(
'session_token',
generate_session_token(),
max_age=30*24*60*60, # 30 Tage!
# Fehlende Sicherheitsflags!
)
# Sensible Daten direkt speichern!
response.set_cookie(
'user_data',
f"{username}:{user.role}:{user.email}",
max_age=30*24*60*60
)
return response
return 'Ungültige Anmeldedaten', 401
# VERWUNDBAR: Remember Me mit vollem Sitzungszugriff
@app.route('/login', methods=['POST'])
def login_remember_vulnerable():
username = request.form['username']
password = request.form['password']
remember = request.form.get('remember', False)
if authenticate(username, password):
session_token = create_full_session(username)
response = make_response(redirect('/dashboard'))
if remember:
# Volle Sitzung für 30 Tage persistiert!
response.set_cookie(
'session',
session_token,
max_age=30*24*60*60, # 30 Tage
httponly=True # Hat zumindest das...
)
else:
response.set_cookie('session', session_token, httponly=True)
return response
// VERWUNDBAR: Java-Servlet mit persistenten sensiblen Cookies
@WebServlet("/login")
public class VerwundbaresLoginServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String username = request.getParameter("username");
String password = request.getParameter("password");
if (authenticate(username, password)) {
// Persistentes Sitzungscookie - schlecht!
Cookie sessionCookie = new Cookie("session", generateToken());
sessionCookie.setMaxAge(30 * 24 * 60 * 60); // 30 Tage
sessionCookie.setPath("/");
// Fehlendes Secure und HttpOnly!
response.addCookie(sessionCookie);
// Benutzerrolle in Cookie speichern!
Cookie roleCookie = new Cookie("role", getUserRole(username));
roleCookie.setMaxAge(30 * 24 * 60 * 60);
response.addCookie(roleCookie);
response.sendRedirect("/dashboard");
}
}
}
// VERWUNDBAR: Anmeldedaten in Cookie speichern
@WebServlet("/remember")
public class VerwundbaresRememberServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException {
String username = request.getParameter("username");
String password = request.getParameter("password");
if (authenticate(username, password)) {
// Anmeldedaten in persistentem Cookie speichern - NIEMALS das tun!
String encoded = Base64.getEncoder().encodeToString(
(username + ":" + password).getBytes()
);
Cookie rememberCookie = new Cookie("credentials", encoded);
rememberCookie.setMaxAge(365 * 24 * 60 * 60); // 1 Jahr!
response.addCookie(rememberCookie);
}
}
}
// VERWUNDBAR: Express mit persistenten sensiblen Cookies
app.post('/login', (req, res) => {
const { username, password, remember } = req.body;
if (authenticate(username, password)) {
const token = generateSessionToken(username);
// Persistentes Cookie mit voller Sitzung!
res.cookie('session', token, {
maxAge: 30 * 24 * 60 * 60 * 1000, // 30 Tage
httpOnly: true
// Fehlendes secure und sameSite!
});
// Sensible Benutzerdaten in Cookie speichern!
res.cookie('userData', JSON.stringify({
username,
email: user.email,
role: user.role
}), {
maxAge: 30 * 24 * 60 * 60 * 1000
});
res.redirect('/dashboard');
}
});
// VERWUNDBAR: Client-seitige persistente Speicherung sensibler Daten
function loginVulnerable(username, password) {
// Sensible Daten in localStorage speichern (schlimmer als Cookies!)
localStorage.setItem('username', username);
localStorage.setItem('authToken', token);
localStorage.setItem('role', user.role);
}
// VERWUNDBAR: Remember Me mit voller Sitzung
app.post('/login', async (req, res) => {
const { username, password, rememberMe } = req.body;
const user = await authenticate(username, password);
if (user) {
const session = createSession(user);
// Volles Sitzungstoken persistiert
const cookieOptions = {
httpOnly: true,
secure: true
};
if (rememberMe) {
cookieOptions.maxAge = 30 * 24 * 60 * 60 * 1000; // 30 Tage
}
res.cookie('session', session.token, cookieOptions);
}
});
Lösungscode
# SICHER: Sitzungscookies für sensible Daten
from flask import Flask, request, make_response, session
import secrets
from datetime import datetime, timedelta
# SICHER: Sitzungscookies verwenden (kein Ablauf = beim Browserschließen gelöscht)
@app.route('/login', methods=['POST'])
def login_safe():
username = request.form['username']
password = request.form['password']
if authenticate(username, password):
response = make_response(redirect('/dashboard'))
# Sitzungscookie - kein max_age bedeutet Ablauf beim Browserschließen
session_token = generate_session_token()
response.set_cookie(
'session',
session_token,
httponly=True,
secure=True,
samesite='Lax'
# Kein max_age = Sitzungscookie
)
# Sensible Daten serverseitig speichern, nicht im Cookie
store_session_data(session_token, {
'user_id': user.id,
'username': username,
'role': user.role,
'created': datetime.utcnow().isoformat()
})
return response
return 'Ungültige Anmeldedaten', 401
# SICHER: Separates "Remember Me"-Token mit eingeschränkten Privilegien
@app.route('/login', methods=['POST'])
def login_with_remember_safe():
username = request.form['username']
password = request.form['password']
remember = request.form.get('remember', False)
user = authenticate(username, password)
if not user:
return 'Ungültige Anmeldedaten', 401
response = make_response(redirect('/dashboard'))
# Sitzungstoken (läuft mit Browser ab)
session_token = create_session(user)
response.set_cookie(
'session',
session_token,
httponly=True,
secure=True,
samesite='Lax'
# Kein Ablauf = Sitzungscookie
)
if remember:
# Separates Remember-Token mit EINGESCHRÄNKTEN Privilegien
remember_token = create_remember_token(user.id)
response.set_cookie(
'remember',
remember_token,
max_age=30*24*60*60, # 30 Tage
httponly=True,
secure=True,
samesite='Strict', # Strikter für persistentes Token
path='/auth/remember' # Eingeschränkter Pfad
)
# Remember-Token-Hash serverseitig speichern
store_remember_token(user.id, hash_token(remember_token))
return response
# Endpunkt zum Wiederherstellen der Sitzung aus Remember-Token
@app.route('/auth/remember', methods=['POST'])
def restore_session():
remember_token = request.cookies.get('remember')
if not remember_token:
return 'Kein Remember-Token', 401
# Remember-Token validieren
user_id = validate_remember_token(remember_token)
if not user_id:
response = make_response('Ungültiges Token', 401)
response.delete_cookie('remember')
return response
# Erneute Authentifizierung für sensible Operationen erforderlich
user = User.query.get(user_id)
response = make_response(redirect('/dashboard'))
# Neues Sitzungstoken erstellen
session_token = create_session(user, limited=True) # Eingeschränkte Sitzung!
response.set_cookie('session', session_token, httponly=True, secure=True)
# Remember-Token rotieren
new_remember = create_remember_token(user_id)
response.set_cookie('remember', new_remember, max_age=30*24*60*60,
httponly=True, secure=True, samesite='Strict')
return response
// SICHER: Java mit ordnungsgemäßer Cookie-Behandlung
@WebServlet("/login")
public class SicheresLoginServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String username = request.getParameter("username");
String password = request.getParameter("password");
boolean remember = "true".equals(request.getParameter("remember"));
User user = authenticate(username, password);
if (user == null) {
response.sendRedirect("/login?error=invalid");
return;
}
// Sitzungscookie - kein MaxAge bedeutet nur Browser-Sitzung
String sessionToken = generateSessionToken();
Cookie sessionCookie = new Cookie("session", sessionToken);
sessionCookie.setHttpOnly(true);
sessionCookie.setSecure(true);
sessionCookie.setPath("/");
// Kein setMaxAge = Sitzungscookie
response.addCookie(sessionCookie);
// Sitzungsdaten serverseitig speichern
SessionStore.save(sessionToken, user.getId(), user.getRole());
if (remember) {
// Separates Remember-Me-Token mit eingeschränktem Bereich
String rememberToken = generateRememberToken();
Cookie rememberCookie = new Cookie("remember", rememberToken);
rememberCookie.setHttpOnly(true);
rememberCookie.setSecure(true);
rememberCookie.setMaxAge(30 * 24 * 60 * 60); // 30 Tage
rememberCookie.setPath("/auth/remember"); // Eingeschränkter Pfad!
response.addCookie(rememberCookie);
// Hash des Remember-Tokens speichern
RememberTokenStore.save(user.getId(), hashToken(rememberToken));
}
response.sendRedirect("/dashboard");
}
}
// SICHER: Spring Security-Konfiguration
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.rememberMe(remember -> remember
// Datenbankgestütztes Token-Repository verwenden
.tokenRepository(persistentTokenRepository())
// Kurze Gültigkeitsdauer
.tokenValiditySeconds(7 * 24 * 60 * 60) // 7 Tage
// Erneute Auth für sensible Operationen erforderlich
.useSecureCookie(true)
)
.sessionManagement(session -> session
// Sitzungscookies nur für Hauptauthentifizierung
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
);
return http.build();
}
@Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
tokenRepository.setDataSource(dataSource);
return tokenRepository;
}
}
// SICHER: Volle Authentifizierung für sensible Operationen erforderlich
@Controller
public class SichererController {
@PreAuthorize("isFullyAuthenticated()") // Nicht remembered!
@PostMapping("/transfer")
public String transfer(@RequestBody TransferRequest request) {
// Nur voll authentifizierte Benutzer können Überweisungen tätigen
return processTransfer(request);
}
@PreAuthorize("isRememberMe() or isFullyAuthenticated()")
@GetMapping("/dashboard")
public String dashboard() {
// Dashboard zugänglich mit Remember-Me
return "dashboard";
}
}
// SICHER: Express mit ordnungsgemäßer Cookie-Behandlung
const crypto = require('crypto');
app.post('/login', async (req, res) => {
const { username, password, rememberMe } = req.body;
const user = await authenticate(username, password);
if (!user) {
return res.status(401).json({ error: 'Ungültige Anmeldedaten' });
}
// Sitzungscookie - läuft beim Browserschließen ab
const sessionToken = generateSessionToken();
res.cookie('session', sessionToken, {
httpOnly: true,
secure: true,
sameSite: 'lax'
// Kein maxAge = Sitzungscookie
});
// Sitzungsdaten serverseitig speichern
await SessionStore.create(sessionToken, {
userId: user.id,
role: user.role,
createdAt: new Date()
});
if (rememberMe) {
// Separates Remember-Token
const rememberToken = generateRememberToken();
const tokenHash = hashToken(rememberToken);
res.cookie('remember', rememberToken, {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 30 * 24 * 60 * 60 * 1000, // 30 Tage
path: '/auth/remember' // Eingeschränkter Pfad
});
// Hash des Tokens speichern
await RememberTokenStore.create(user.id, tokenHash);
}
res.json({ success: true });
});
// Sitzung aus Remember-Token wiederherstellen
app.post('/auth/remember', async (req, res) => {
const rememberToken = req.cookies.remember;
if (!rememberToken) {
return res.status(401).json({ error: 'Kein Remember-Token' });
}
const tokenHash = hashToken(rememberToken);
const stored = await RememberTokenStore.findByHash(tokenHash);
if (!stored) {
res.clearCookie('remember');
return res.status(401).json({ error: 'Ungültiges Token' });
}
const user = await User.findById(stored.userId);
// EINGESCHRÄNKTE Sitzung erstellen
const sessionToken = generateSessionToken();
res.cookie('session', sessionToken, {
httpOnly: true,
secure: true,
sameSite: 'lax'
});
await SessionStore.create(sessionToken, {
userId: user.id,
role: user.role,
fromRemember: true, // Als wiederhergestellte Sitzung markieren
createdAt: new Date()
});
// Remember-Token rotieren
const newRememberToken = generateRememberToken();
await RememberTokenStore.update(stored.id, hashToken(newRememberToken));
res.cookie('remember', newRememberToken, {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 30 * 24 * 60 * 60 * 1000,
path: '/auth/remember'
});
res.json({ success: true });
});
// Middleware für volle Authentifizierung bei sensiblen Operationen
const requireFullAuth = async (req, res, next) => {
const session = await SessionStore.find(req.cookies.session);
if (!session) {
return res.status(401).json({ error: 'Nicht authentifiziert' });
}
if (session.fromRemember) {
return res.status(403).json({
error: 'Volle Authentifizierung erforderlich',
requireReauth: true
});
}
next();
};
app.post('/transfer', requireFullAuth, async (req, res) => {
// Nur voll authentifizierte Benutzer können überweisen
});
// Hilfsfunktionen
function generateRememberToken() {
return crypto.randomBytes(32).toString('hex');
}
function hashToken(token) {
return crypto.createHash('sha256').update(token).digest('hex');
}
Ausgenutzt in der Praxis
Session-Hijacking über persistente Cookies
Angreifer haben persistente Sitzungscookies von gemeinsam genutzten Computern und Malware-Infektionen geerntet, um Benutzerkonten zu übernehmen.
Anmeldedaten-Diebstahl aus Cookies
Anwendungen, die Anmeldedaten oder sensible Tokens in persistenten Cookies speichern, wurden durch Cookie-Diebstahl-Angriffe ausgenutzt.
Cross-Site Cookie-Angriffe
Persistente Cookies ohne ordnungsgemäße Flags wurden durch XSS- und Netzwerk-Abhörangriffe gestohlen.
Tools zum Testen/Ausnutzen
-
Browser-Entwicklertools — Cookie-Attribute inspizieren.
-
Burp Suite — Cookie-Behandlung analysieren.
-
Cookie-Editor — Browser-Erweiterung zum Testen.
-
Netzwerk-Inspektionstools für Cookie-Abfangen.
CVE-Beispiele
-
CVE-2019-11358 — Persistente Cookie-Probleme.
-
CVE-2020-8945 — Cookie-basierte Sitzungsoffenlegung.
-
Zahlreiche anwendungsspezifische Cookie-Schwachstellen.
Referenzen
-
MITRE. "CWE-539: Use of Persistent Cookies Containing Sensitive Information." https://cwe.mitre.org/data/definitions/539.html
-
OWASP. "Session Management Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html