Sensibler Cookie mit unsachgemäßem SameSite-Attribut

Beschreibung

Sensibler Cookie mit unsachgemäßem SameSite-Attribut tritt auf, wenn das SameSite-Attribut für sensible Cookies nicht gesetzt ist oder ein unsicherer Wert verwendet wird. Das SameSite-Attribut steuert die domainubergreifende Cookie-Übertragung mit drei möglichen Werten: Lax, Strict oder None. Die Verwendung von "None" ohne zusätzliche Schutzmaßnahmen wie Anti-CSRF-Token ermöglicht potenzielle Cross-Site Request Forgery (CSRF)-Angriffe. Wenn SameSite nicht ordnungsgemäß konfiguriert ist, können Browser Cookies mit Cross-Site-Anfragen senden, was Angreifern potenziell ermöglicht, unbefugte Aktionen im Namen authentifizierter Benutzer durchzuführen.

Risiko

Unsachgemäße SameSite-Konfiguration hat erhebliche Sicherheitsauswirkungen. CSRF-Angriffe werden möglich. Session-Cookies können Cross-Site gesendet werden. Unbefugte Aktionen können durchgeführt werden. Benutzerkonten können kompromittiert werden. Finanztransaktionen können manipuliert werden. Daten können ohne Zustimmung modifiziert werden. Datenschutz kann verletzt werden. Kontoubernahme kann auftreten.

Lösung

Setzen Sie das SameSite-Attribut sensibler Cookies auf 'Lax' oder 'Strict'. Lax erlaubt Cookies für Top-Level-Navigation über sichere HTTP-Methoden (GET, HEAD, OPTIONS, TRACE), beschrankt aber zustandsändernde Anfragen. Strict bietet maximalen Schutz, kann aber die Benutzererfahrung beeinträchtigen. Implementieren Sie zusätzlich Anti-CSRF-Token für zustandsändernde Operationen unabhängig von der SameSite-Einstellung.

Häufige Auswirkungen

AuswirkungDetails
ZugriffskontrolleUmfang: Zugriffskontrolle

Schutzmechanismus umgehen - CSRF-Schutzmaßnahmen können umgangen werden.
IntegritatUmfang: Integritat

Anwendungsdaten ändern - Unbefugte Zustandsänderungen können auftreten.
VertraulichkeitUmfang: Vertraulichkeit

Anwendungsdaten lesen - Sensible Operationen können ausgelöst werden.

Beispielcode und Lösung

Verwundbarer Code

// VERWUNDBAR: Session-Cookie ohne SameSite-Attribut

const express = require('express');
const session = require('express-session');
const app = express();

// VERWUNDBAR: Kein SameSite-Attribut angegeben
app.use(session({
    secret: 'session-secret',
    resave: false,
    saveUninitialized: true,
    cookie: {
        httpOnly: true,
        secure: true
        // VERWUNDBAR: SameSite nicht gesetzt
        // Standardmäßig Browser-Verhalten (kann 'None' sein)
    }
}));

// VERWUNDBAR: Zustandsändernde Aktion ohne CSRF-Schutz
app.post('/transfer', (req, res) => {
    const { to, amount } = req.body;

    // Cookie wird mit Cross-Site-Anfrage gesendet!
    if (req.session.user) {
        transferFunds(req.session.user, to, amount);
        res.json({ success: true });
    }
});

// Bosartige Seite des Angreifers kann Uberweisung auslosen:
// <form action="https://bank.com/transfer" method="POST">
//   <input name="to" value="attacker">
//   <input name="amount" value="10000">
// </form>
// <script>document.forms[0].submit();</script>
# VERWUNDBAR: Flask-Session ohne SameSite

from flask import Flask, session, request
app = Flask(__name__)
app.secret_key = 'secret-key'

# VERWUNDBAR: Keine SameSite-Konfiguration
app.config['SESSION_COOKIE_SAMESITE'] = None  # Explizit deaktiviert!
app.config['SESSION_COOKIE_SECURE'] = True

@app.route('/change-email', methods=['POST'])
def change_email():
    # VERWUNDBAR: Keine CSRF-Token-Validierung
    if 'user_id' in session:
        new_email = request.form.get('email')
        # Session-Cookie wird mit Cross-Site-POST gesendet
        update_user_email(session['user_id'], new_email)
        return 'Email aktualisiert'
    return 'Nicht autorisiert', 401
<?php
// VERWUNDBAR: PHP-Session-Cookie ohne SameSite

// VERWUNDBAR: Standard-Session-Cookie-Einstellungen
session_start();

// Oder explizit verwundbare Konfiguration
ini_set('session.cookie_samesite', '');  // Leer = kein SameSite

// VERWUNDBAR: Zustandsändernde Aktion
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_SESSION['user_id'])) {
    // Kein CSRF-Schutz
    $new_password = $_POST['password'];

    // Cookie wird mit Cross-Site-Anfrage gesendet!
    change_password($_SESSION['user_id'], $new_password);
    echo "Passwort geändert";
}

// Seite des Angreifers löst Passwortwechsel aus
?>
// VERWUNDBAR: Java-Servlet-Cookie ohne SameSite

import javax.servlet.http.*;

public class VulnerableSessionServlet extends HttpServlet {

    protected void doPost(HttpServletRequest request,
                          HttpServletResponse response) {
        // VERWUNDBAR: Session-Cookie ohne SameSite
        HttpSession session = request.getSession(true);

        // Cookie ohne SameSite-Attribut erstellen
        Cookie sessionCookie = new Cookie("JSESSIONID", session.getId());
        sessionCookie.setHttpOnly(true);
        sessionCookie.setSecure(true);
        // VERWUNDBAR: Kein SameSite gesetzt
        response.addCookie(sessionCookie);

        // Zustandsändernde Aktion ohne CSRF-Schutz
        if (session.getAttribute("user") != null) {
            String action = request.getParameter("action");
            performAction(action);  // Verwundbar für CSRF
        }
    }
}

Sichere Lösung

// SICHER: Session-Cookie mit ordnungsgemäßem SameSite-Attribut

const express = require('express');
const session = require('express-session');
const csrf = require('csurf');
const app = express();

// SICHER: SameSite-Attribut auf Strict gesetzt
app.use(session({
    secret: 'session-secret',
    resave: false,
    saveUninitialized: false,
    cookie: {
        httpOnly: true,
        secure: true,
        sameSite: 'strict',  // SICHER: Striktes SameSite
        maxAge: 3600000      // 1 Stunde
    }
}));

// SICHER: Zusätzlicher CSRF-Schutz
const csrfProtection = csrf({ cookie: false });

app.use(csrfProtection);

// SICHER: Geschutzte zuständsändernde Aktion
app.post('/transfer', csrfProtection, (req, res) => {
    const { to, amount } = req.body;

    // SICHER: Cookie wird nicht Cross-Site gesendet dank SameSite=Strict
    // SICHER: CSRF-Token wird ebenfalls verifiziert
    if (req.session.user) {
        transferFunds(req.session.user, to, amount);
        res.json({ success: true });
    }
});

// Für APIs die etwas Cross-Site-Funktionalitat benötigen
// SICHER: Lax mit zusätzlichem CSRF-Schutz verwenden
app.use('/api', session({
    secret: 'api-session-secret',
    cookie: {
        httpOnly: true,
        secure: true,
        sameSite: 'lax',  // SICHER: Lax für API-Kompatibilitat
    }
}));

// SICHER: CSRF immer für zuständsändernde Operationen verifizieren
app.post('/api/action', csrfProtection, (req, res) => {
    // Geschutzt durch sowohl SameSite=Lax als auch CSRF-Token
    performAction(req.body);
});
# SICHER: Flask-Session mit ordnungsgemäßem SameSite

from flask import Flask, session, request
from flask_wtf.csrf import CSRFProtect

app = Flask(__name__)
app.secret_key = 'secret-key'

# SICHER: CSRF-Schutz aktivieren
csrf = CSRFProtect(app)

# SICHER: SameSite=Strict konfigurieren
app.config['SESSION_COOKIE_SAMESITE'] = 'Strict'
app.config['SESSION_COOKIE_SECURE'] = True
app.config['SESSION_COOKIE_HTTPONLY'] = True

@app.route('/change-email', methods=['POST'])
def change_email():
    # SICHER: CSRF-Token automatisch von Flask-WTF validiert
    if 'user_id' in session:
        new_email = request.form.get('email')
        update_user_email(session['user_id'], new_email)
        return 'Email aktualisiert'
    return 'Nicht autorisiert', 401

# Alternative für APIs: Lax mit explizitem CSRF
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'

@app.route('/api/action', methods=['POST'])
@csrf.exempt  # Nur wenn anderer CSRF-Mechanismus verwendet wird
def api_action():
    # SICHER: Benutzerdefinierten CSRF-Header für API verifizieren
    csrf_header = request.headers.get('X-CSRF-Token')
    if not verify_csrf_token(csrf_header, session.get('csrf_token')):
        return 'CSRF-Validierung fehlgeschlagen', 403

    # Aktion verarbeiten
    return 'Erfolg'
<?php
// SICHER: PHP-Session-Cookie mit SameSite

// SICHER: SameSite vor Session-Start konfigurieren
ini_set('session.cookie_samesite', 'Strict');
ini_set('session.cookie_secure', '1');
ini_set('session.cookie_httponly', '1');

session_start();

// SICHER: CSRF-Token generieren
if (!isset($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

// SICHER: CSRF-Token für zuständsändernde Anfragen verifizieren
function verify_csrf_token($token) {
    return isset($_SESSION['csrf_token']) &&
           hash_equals($_SESSION['csrf_token'], $token);
}

// SICHER: Geschutzte zuständsändernde Aktion
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_SESSION['user_id'])) {
    // SICHER: CSRF-Token verifizieren
    $csrf_token = $_POST['csrf_token'] ?? '';
    if (!verify_csrf_token($csrf_token)) {
        http_response_code(403);
        die('CSRF-Validierung fehlgeschlagen');
    }

    $new_password = $_POST['password'];
    change_password($_SESSION['user_id'], $new_password);
    echo "Passwort geändert";
}

// SICHER: CSRF-Token in Formulare einfugen
function csrf_field() {
    return '<input type="hidden" name="csrf_token" value="' .
           htmlspecialchars($_SESSION['csrf_token']) . '">';
}
?>
// SICHER: Java-Servlet-Cookie mit SameSite

import javax.servlet.http.*;

public class SecureSessionServlet extends HttpServlet {

    protected void doPost(HttpServletRequest request,
                          HttpServletResponse response) {
        HttpSession session = request.getSession(true);

        // SICHER: Session-Cookie mit SameSite=Strict setzen
        String sessionId = session.getId();
        String cookieValue = String.format(
            "JSESSIONID=%s; HttpOnly; Secure; SameSite=Strict; Path=/",
            sessionId
        );
        response.setHeader("Set-Cookie", cookieValue);

        // SICHER: CSRF-Token verifizieren
        String csrfToken = request.getHeader("X-CSRF-Token");
        String sessionCsrf = (String) session.getAttribute("csrfToken");

        if (csrfToken == null || !csrfToken.equals(sessionCsrf)) {
            response.setStatus(403);
            return;
        }

        // Zustandsändernde Aktion jetzt geschützt
        if (session.getAttribute("user") != null) {
            String action = request.getParameter("action");
            performAction(action);
        }
    }

    // SICHER: CSRF-Token für Session generieren
    protected void doGet(HttpServletRequest request,
                         HttpServletResponse response) {
        HttpSession session = request.getSession(true);

        if (session.getAttribute("csrfToken") == null) {
            String token = generateSecureToken();
            session.setAttribute("csrfToken", token);
        }

        // Token in Antwort für Client-Verwendung einfugen
        response.setHeader("X-CSRF-Token",
            (String) session.getAttribute("csrfToken"));
    }
}

CVE-Beispiele

SameSite-Cookie-Schwachstellen wurden in verschiedenen Webanwendungen gefunden, bei denen fehlende oder unsachgemäß konfigurierte SameSite-Attribute CSRF-Angriffe gegen authentifizierte Benutzer ermöglichten.


Verwandte CWEs

  • CWE-923: Unsachgemäße Einschränkung des Kommunikationskanals auf vorgesehene Endpunkte (ubergeordnet)
  • CWE-352: Cross-Site Request Forgery (kann vorausgehen)
  • CWE-614: Sensibler Cookie in HTTPS-Sitzung ohne 'Secure'-Attribut (verwandt)
  • CAPEC-62: Cross-Site Request Forgery (Angriffsmuster)

Referenzen

  1. MITRE Corporation. "CWE-1275: Sensitive Cookie with Improper SameSite Attribute." https://cwe.mitre.org/data/definitions/1275.html
  2. OWASP. "SameSite Cookie Attribute"
  3. RFC 6265bis. "Cookies: HTTP State Management Mechanism"