Vertrauen auf Cookies ohne Validierung und Integritätsprüfung

Beschreibung

Vertrauen auf Cookies ohne Validierung und Integritätsprüfung ist eine Schwachstelle, bei der eine Webanwendung sich auf die Existenz, das Format oder die Werte von Cookies für sicherheitskritische Operationen verlässt, ohne ordnungsgemäß zu verifizieren, dass diese Einstellungen gültig sind und nicht manipuliert wurden. Cookies werden auf der Client-Seite gespeichert und können von Angreifern mit Browser-Entwicklertools, Proxy-Interceptoren oder benutzerdefinierten HTTP-Clients leicht eingesehen, modifiziert oder gefälscht werden. Wenn Anwendungen Cookie-Werten für Authentifizierungsstatus, Benutzerrollen, Berechtigungen oder andere Sicherheitsentscheidungen ohne serverseitige Validierung oder Integritätsverifizierung vertrauen, können Angreifer diese Werte manipulieren, um Sicherheitskontrollen zu umgehen.

Risiko

Das Vertrauen auf nicht validierte Cookies erzeugt schwere Sicherheitsschwachstellen. Angreifer können Authentifizierungs-Cookies modifizieren, um Login-Anforderungen zu umgehen. Rollen- oder Berechtigungs-Cookies können geändert werden, um Privilegien vom normalen Benutzer zum Administrator zu eskalieren. Sitzungsdaten, die ohne Integritätsschutz in Cookies gespeichert sind, können manipuliert werden, um andere Benutzer zu imitieren. Warenkorb- oder Preisinformationen in Cookies können für Finanzbetrug modifiziert werden. Cookie-basierte Token ohne kryptografische Validierung können gefälscht werden. Die Leichtigkeit der Cookie-Manipulation bedeutet, dass diese Angriffe minimale Fähigkeiten erfordern, was sie hochgradig ausnutzbar macht. In Kombination mit anderen Schwachstellen kann Cookie-Manipulation Injection-Angriffe durch Einfügen bösartiger Werte ermöglichen.

Lösung

Vermeiden Sie die direkte Speicherung sicherheitskritischer Informationen in Cookies. Wenn Cookie-Daten verwendet werden müssen, implementieren Sie kryptografische Integritätsprüfungen mit HMAC oder authentifizierter Verschlüsselung, um Manipulationen zu erkennen. Speichern Sie sensiblen Zustand auf der Serverseite und verwenden Sie nur eine Sitzungskennung im Cookie. Validieren Sie alle Cookie-Daten gegen serverseitige Aufzeichnungen, bevor Sicherheitsentscheidungen getroffen werden. Implementieren Sie Replay-Schutz mit unvorhersehbaren, zeitlich begrenzten Token. Verwenden Sie die Secure- und HttpOnly-Cookie-Flags. Setzen Sie entsprechende SameSite-Attribute, um CSRF zu verhindern. Vertrauen Sie niemals clientseitigen Daten für Autorisierungsentscheidungen - verifizieren Sie immer gegen serverseitigen Zustand.

Häufige Auswirkungen

AuswirkungDetails
IntegritätBereich: Integrität

Anwendungsdaten ändern - Angreifer können Cookie-Werte manipulieren, um den Anwendungszustand zu ändern, Transaktionen zu modifizieren oder bösartigen Inhalt zu injizieren.
ZugriffskontrolleBereich: Zugriffskontrolle

Privilegien erlangen oder Identität annehmen - Rollen- oder Berechtigungsinformationen, die in Cookies gespeichert sind, können modifiziert werden, um Privilegien auf Administratorebene zu eskalieren.
VerfügbarkeitBereich: Verfügbarkeit

Schutzmechanismus umgehen - Authentifizierungs- und Autorisierungsmechanismen, die sich auf Cookie-Werte verlassen, können durch Cookie-Manipulation vollständig umgangen werden.

Beispielcode

Verwundbarer Code

// Verwundbar: Vertrauen auf Cookie-Werte für Autorisierung
import javax.servlet.http.*;

public class VulnerableAuthServlet extends HttpServlet {

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

        // Verwundbar: Rolle direkt aus Cookie lesen
        Cookie[] cookies = request.getCookies();
        String userRole = "guest";

        if (cookies != null) {
            for (Cookie c : cookies) {
                if (c.getName().equals("role")) {
                    // Verwundbar: Cookie-Wert ohne Validierung vertrauen
                    userRole = c.getValue();
                }
                if (c.getName().equals("isAuthenticated")) {
                    // Verwundbar: Authentifizierungsstatus aus Cookie
                    if (c.getValue().equals("true")) {
                        // Benutzer gilt als authentifiziert nur basierend auf Cookie!
                    }
                }
            }
        }

        // Verwundbar: Autorisierung basierend auf nicht validiertem Cookie
        if (userRole.equals("admin")) {
            // Admin-Zugriff basierend auf leicht fälschbarem Cookie gewährt!
            displayAdminPanel(response);
        } else {
            displayUserPanel(response);
        }
    }

    // Verwundbar: Rolle in Cookie setzen
    protected void doLogin(HttpServletRequest request, HttpServletResponse response,
                          String username, String password) {
        if (authenticate(username, password)) {
            // Verwundbar: Rolle im clientseitigen Cookie speichern
            Cookie roleCookie = new Cookie("role", getUserRole(username));
            Cookie authCookie = new Cookie("isAuthenticated", "true");
            Cookie userIdCookie = new Cookie("userId", getUserId(username));

            response.addCookie(roleCookie);
            response.addCookie(authCookie);
            response.addCookie(userIdCookie);
            // Angreifer kann diese Cookies modifizieren um Privilegien zu eskalieren
        }
    }
}
<?php
// Verwundbar: PHP-Authentifizierung mit Cookies ohne Validierung

// Verwundbar: Login setzt ungeschützte Cookies
function vulnerable_login($username, $password) {
    if (authenticate($username, $password)) {
        $user = get_user($username);

        // Verwundbar: Sensible Daten in Cookies speichern
        setcookie('user_id', $user['id'], time() + 3600);
        setcookie('username', $user['username'], time() + 3600);
        setcookie('role', $user['role'], time() + 3600);
        setcookie('is_admin', $user['is_admin'] ? '1' : '0', time() + 3600);

        // Angreifer kann modifizieren: role=admin, is_admin=1
        return true;
    }
    return false;
}

// Verwundbar: Autorisierung basierend auf Cookie-Werten
function check_admin_access() {
    // Verwundbar: Cookie direkt vertrauen
    if (isset($_COOKIE['is_admin']) && $_COOKIE['is_admin'] === '1') {
        return true;  // Admin-Zugriff basierend auf gefälschtem Cookie gewährt!
    }
    return false;
}

// Verwundbar: Benutzer aus Cookie abrufen
function get_current_user_id() {
    // Verwundbar: Benutzer-ID aus Cookie kann modifiziert werden
    return isset($_COOKIE['user_id']) ? $_COOKIE['user_id'] : null;

    // Angreifer kann auf Daten anderer Benutzer zugreifen durch Ändern des user_id-Cookies
}

// Verwundbar: Preisberechnung vertraut Cookie
function calculate_total() {
    // Verwundbar: Preis im Cookie gespeichert
    $price = isset($_COOKIE['item_price']) ? $_COOKIE['item_price'] : 0;
    $quantity = isset($_COOKIE['quantity']) ? $_COOKIE['quantity'] : 1;

    // Angreifer kann item_price=0.01 im Cookie setzen!
    return $price * $quantity;
}
?>
// Verwundbar: Node.js Express mit nicht validierten Cookies
const express = require('express');
const cookieParser = require('cookie-parser');

const app = express();
app.use(cookieParser());

// Verwundbar: Login setzt ungeschützte Cookies
app.post('/login', async (req, res) => {
    const { username, password } = req.body;

    if (await authenticate(username, password)) {
        const user = await getUser(username);

        // Verwundbar: Sensible Daten in Klartext-Cookies speichern
        res.cookie('userId', user.id);
        res.cookie('role', user.role);
        res.cookie('permissions', JSON.stringify(user.permissions));
        res.cookie('isAuthenticated', 'true');

        res.redirect('/dashboard');
    } else {
        res.status(401).send('Ungültige Anmeldedaten');
    }
});

// Verwundbar: Autorisierungs-Middleware vertraut Cookies
function requireAdmin(req, res, next) {
    // Verwundbar: Rolle aus Cookie ohne Validierung
    if (req.cookies.role === 'admin') {
        next();  // Admin-Zugriff basierend auf gefälschtem Cookie!
    } else {
        res.status(403).send('Zugriff verweigert');
    }
}

// Verwundbar: Benutzerdaten aus Cookie abrufen
app.get('/profile', (req, res) => {
    // Verwundbar: Benutzer-ID aus Cookie kann manipuliert werden
    const userId = req.cookies.userId;
    // Angreifer kann auf jedes Benutzerprofil zugreifen durch Ändern des userId-Cookies
    getUserProfile(userId).then(profile => res.json(profile));
});

// Verwundbar: Rabatt basierend auf Cookie
app.post('/checkout', (req, res) => {
    // Verwundbar: Rabattprozentsatz aus Cookie
    const discount = parseFloat(req.cookies.discount) || 0;
    // Angreifer kann discount=100 im Cookie setzen für kostenlose Artikel!
    const total = calculateTotal(req.body.items) * (1 - discount / 100);
    processPayment(total);
});

Lösungscode

// Behoben: Serverseitige Sitzung mit signierten Cookies
import javax.servlet.http.*;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

public class SecureAuthServlet extends HttpServlet {
    private static final String HMAC_SECRET = System.getenv("COOKIE_SECRET");

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

        // Behoben: Sitzung aus serverseitigem Speicher abrufen
        HttpSession session = request.getSession(false);

        if (session == null || session.getAttribute("userId") == null) {
            response.sendRedirect("/login");
            return;
        }

        // Behoben: Rolle aus serverseitiger Sitzung, nicht aus Cookie
        String userRole = (String) session.getAttribute("role");

        if ("admin".equals(userRole)) {
            displayAdminPanel(response);
        } else {
            displayUserPanel(response);
        }
    }

    protected void doLogin(HttpServletRequest request, HttpServletResponse response,
                          String username, String password) throws IOException {
        if (authenticate(username, password)) {
            // Behoben: Daten in serverseitiger Sitzung speichern
            HttpSession session = request.getSession(true);
            session.setAttribute("userId", getUserId(username));
            session.setAttribute("username", username);
            session.setAttribute("role", getUserRole(username));

            // Behoben: Nur Sitzungs-ID im Cookie (vom Container verwaltet)
            // Sitzungs-ID ist in modernen Containern standardmäßig HttpOnly

            response.sendRedirect("/dashboard");
        }
    }

    // Behoben: Signiertes Cookie für zuständslose Daten
    private void setSignedCookie(HttpServletResponse response, String name, String value) {
        String signature = computeHmac(value);
        String signedValue = value + "." + signature;

        Cookie cookie = new Cookie(name, signedValue);
        cookie.setHttpOnly(true);
        cookie.setSecure(true);
        cookie.setPath("/");
        response.addCookie(cookie);
    }

    private String getVerifiedCookieValue(HttpServletRequest request, String name) {
        Cookie[] cookies = request.getCookies();
        if (cookies == null) return null;

        for (Cookie c : cookies) {
            if (c.getName().equals(name)) {
                String[] parts = c.getValue().split("\\.");
                if (parts.length != 2) return null;

                String value = parts[0];
                String signature = parts[1];

                // Behoben: Signatur verifizieren
                if (computeHmac(value).equals(signature)) {
                    return value;
                }
            }
        }
        return null;
    }

    private String computeHmac(String data) {
        try {
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(new SecretKeySpec(HMAC_SECRET.getBytes(), "HmacSHA256"));
            return Base64.getEncoder().encodeToString(mac.doFinal(data.getBytes()));
        } catch (Exception e) {
            throw new RuntimeException("HMAC-Berechnung fehlgeschlagen", e);
        }
    }
}
<?php
// Behoben: PHP mit serverseitigen Sitzungen und signierten Cookies

session_start();

// Konfiguration
define('COOKIE_SECRET', getenv('COOKIE_SECRET'));

// Behoben: Login mit serverseitigen Sitzungen
function secure_login($username, $password) {
    if (authenticate($username, $password)) {
        $user = get_user($username);

        // Behoben: Sensible Daten in serverseitiger Sitzung speichern
        $_SESSION['user_id'] = $user['id'];
        $_SESSION['username'] = $user['username'];
        $_SESSION['role'] = $user['role'];
        $_SESSION['is_admin'] = $user['is_admin'];
        $_SESSION['login_time'] = time();

        // Behoben: Sitzungs-ID regenerieren um Fixierung zu verhindern
        session_regenerate_id(true);

        return true;
    }
    return false;
}

// Behoben: Autorisierung basierend auf serverseitiger Sitzung
function check_admin_access() {
    // Behoben: Sitzung prüfen, nicht Cookie
    return isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
}

// Behoben: Benutzer aus Sitzung abrufen
function get_current_user_id() {
    // Behoben: Benutzer-ID aus serverseitiger Sitzung
    return isset($_SESSION['user_id']) ? $_SESSION['user_id'] : null;
}

// Behoben: Signiertes Cookie für zuständslose Daten (falls benötigt)
function set_signed_cookie($name, $value, $expiry) {
    $signature = hash_hmac('sha256', $value, COOKIE_SECRET);
    $signed_value = base64_encode($value) . '.' . $signature;

    setcookie($name, $signed_value, [
        'expires' => $expiry,
        'path' => '/',
        'secure' => true,
        'httponly' => true,
        'samesite' => 'Strict'
    ]);
}

function get_verified_cookie($name) {
    if (!isset($_COOKIE[$name])) {
        return null;
    }

    $parts = explode('.', $_COOKIE[$name]);
    if (count($parts) !== 2) {
        return null;
    }

    $value = base64_decode($parts[0]);
    $signature = $parts[1];

    // Behoben: Signatur mit konstantem Zeitvergleich verifizieren
    $expected = hash_hmac('sha256', $value, COOKIE_SECRET);
    if (hash_equals($expected, $signature)) {
        return $value;
    }

    return null;  // Manipuliertes Cookie
}

// Behoben: Warenkorb mit serverseitigem Speicher
function add_to_cart($product_id, $quantity) {
    // Behoben: Preis vom Server holen, nicht vom Client
    $product = get_product($product_id);
    if (!$product) return false;

    if (!isset($_SESSION['cart'])) {
        $_SESSION['cart'] = [];
    }

    $_SESSION['cart'][$product_id] = [
        'quantity' => $quantity,
        'price' => $product['price']  // Serverseitiger Preis
    ];

    return true;
}

function calculate_total() {
    // Behoben: Aus serverseitigen Sitzungsdaten berechnen
    $total = 0;
    if (isset($_SESSION['cart'])) {
        foreach ($_SESSION['cart'] as $item) {
            $total += $item['price'] * $item['quantity'];
        }
    }
    return $total;
}
?>
// Behoben: Node.js Express mit signierten Sitzungen
const express = require('express');
const session = require('express-session');
const crypto = require('crypto');

const app = express();

// Behoben: Signierte Sitzungen verwenden
app.use(session({
    secret: process.env.SESSION_SECRET,
    resave: false,
    saveUninitialized: false,
    cookie: {
        secure: true,  // Nur HTTPS
        httpOnly: true,  // Kein JavaScript-Zugriff
        sameSite: 'strict',  // CSRF-Schutz
        maxAge: 3600000  // 1 Stunde
    }
}));

// Behoben: Login speichert Daten in serverseitiger Sitzung
app.post('/login', async (req, res) => {
    const { username, password } = req.body;

    if (await authenticate(username, password)) {
        const user = await getUser(username);

        // Behoben: In serverseitiger Sitzung speichern
        req.session.userId = user.id;
        req.session.username = user.username;
        req.session.role = user.role;
        req.session.permissions = user.permissions;
        req.session.loginTime = Date.now();

        // Behoben: Sitzungs-ID regenerieren
        req.session.regenerate((err) => {
            if (err) {
                return res.status(500).send('Sitzungsfehler');
            }
            res.redirect('/dashboard');
        });
    } else {
        res.status(401).send('Ungültige Anmeldedaten');
    }
});

// Behoben: Autorisierungs-Middleware verwendet Sitzung
function requireAdmin(req, res, next) {
    // Behoben: Rolle aus serverseitiger Sitzung
    if (req.session && req.session.role === 'admin') {
        next();
    } else {
        res.status(403).send('Zugriff verweigert');
    }
}

// Behoben: Benutzeridentifikation aus Sitzung
app.get('/profile', (req, res) => {
    if (!req.session.userId) {
        return res.status(401).send('Nicht authentifiziert');
    }

    // Behoben: Benutzer-ID aus serverseitiger Sitzung
    getUserProfile(req.session.userId)
        .then(profile => res.json(profile));
});

// Behoben: Rabatt serverseitig angewendet
app.post('/checkout', async (req, res) => {
    if (!req.session.userId) {
        return res.status(401).send('Nicht authentifiziert');
    }

    // Behoben: Rabatt aus serverseitigen Benutzerdaten holen
    const user = await getUser(req.session.userId);
    const discount = user.discountPercentage || 0;

    const total = calculateTotal(req.body.items) * (1 - discount / 100);
    await processPayment(req.session.userId, total);

    res.json({ total });
});

// Behoben: Signierter Cookie-Helfer für zuständslose Daten
function signedCookie(name, value, secret) {
    const signature = crypto
        .createHmac('sha256', secret)
        .update(value)
        .digest('base64');
    return `${Buffer.from(value).toString('base64')}.${signature}`;
}

function verifySignedCookie(cookieValue, secret) {
    const [encodedValue, signature] = cookieValue.split('.');
    if (!encodedValue || !signature) return null;

    const value = Buffer.from(encodedValue, 'base64').toString();
    const expectedSignature = crypto
        .createHmac('sha256', secret)
        .update(value)
        .digest('base64');

    // Behoben: Konstantzeit-Vergleich
    if (crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))) {
        return value;
    }
    return null;
}

module.exports = app;

CVE-Beispiele

  • CVE-2016-4437: Apache Shiro Cookie-basierte RememberMe-Schwachstelle ermöglichte Angreifern die Umgehung der Authentifizierung.
  • CVE-2015-8562: Joomla Sitzungs-Cookie-Schwachstelle ermöglichte Remote-Code-Ausführung.
  • CVE-2012-3137: Oracle HTTP Server Cookie-Manipulationsschwachstelle.

Referenzen

  1. MITRE Corporation. "CWE-565: Reliance on Cookies without Validation and Integrity Checking." https://cwe.mitre.org/data/definitions/565.html
  2. OWASP. "Session Management Cheat Sheet."
  3. OWASP. "Testing for Cookies Attributes."