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

AuswirkungDetails
VertraulichkeitBereich: Datenoffenlegung

Sensible Informationen bleiben auf der Festplatte, wo sie zugänglich sind.
AuthentifizierungBereich: Session-Hijacking

Persistente Sitzungstokens ermöglichen langfristigen unbefugten Zugriff.
DatenschutzBereich: 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.

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

  1. MITRE. "CWE-539: Use of Persistent Cookies Containing Sensitive Information." https://cwe.mitre.org/data/definitions/539.html

  2. OWASP. "Session Management Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html