Content Security Policy (CSP) gegen XSS härten

Frage an Claude:

Sicherstellen, dass die CSP effektiv gegen XSS-Angriffe abwehrt

Eine wirksame CSP gegen XSS-Angriffe braucht mehrere Bausteine. Hier die wichtigsten Punkte:

1. script-src ist der wichtigste Hebel

Content-Security-Policy: script-src 'self' 'nonce-{RANDOM}' 'strict-dynamic';

Vermeiden:

  • 'unsafe-inline' – hebt den XSS-Schutz praktisch komplett auf
  • 'unsafe-eval' – erlaubt eval(), new Function(), etc.
  • Breite Whitelists wie https://*.cdn.com – oft umgehbar über JSONP-Endpoints, offene Redirects oder alte Bibliotheksversionen auf derselben Domain (siehe CSP-Bypass-Listen)

2. Nonce- oder Hash-basierter Ansatz statt Whitelist

Whitelist-basierte CSPs gelten mittlerweile als unsicher, weil sich auf vielen CDNs (Google Hosted Libraries, Cloudflare, etc.) Skript-Gadgets finden, die eine Umgehung erlauben. Besser:

  • Nonce: Pro Request ein zufälliger, kryptografisch sicherer Wert (nonce-xxx), der bei jedem <script>-Tag serverseitig eingefügt wird
  • Hash: SHA256-Hash des exakten Skriptinhalts (nur praktikabel bei statischen/wenigen Inline-Skripten)
  • 'strict-dynamic' zusammen mit Nonce/Hash erlaubt, dass von vertrauenswürdigen Skripten dynamisch nachgeladene Skripte automatisch vertraut werden – ohne dass man jede Domain einzeln whitelisten muss

3. Weitere wichtige Direktiven

default-src 'self';
object-src 'none';
base-uri 'self';
frame-ancestors 'self';
form-action 'self';
  • object-src 'none' – blockiert Flash/Plugin-basierte XSS-Vektoren
  • base-uri 'self' – verhindert Manipulation von <base href>, was sonst relative Pfade kapern kann
  • frame-ancestors – schützt zusätzlich vor Clickjacking (Ersatz für X-Frame-Options)

4. Typische Schwachstellen der eigenen CSP prüfen

  • JSONP-Endpoints auf erlaubten Domains (callback=alert(1))
  • Angular/Vue mit unsafe-eval – manche Frameworks brauchen das standardmäßig, es gibt aber CSP-kompatible Build-Modi
  • Base-Tag-Injection, wenn base-uri fehlt
  • Wildcard-Subdomains (*.example.com), falls Subdomain-Takeover möglich ist

5. Testen und überwachen

  • Content-Security-Policy-Report-Only Header parallel einsetzen, um Verstöße zu loggen, bevor man scharf schaltet
  • report-uri / report-to Direktive für Reporting-Endpoint nutzen
  • Tools wie CSP Evaluator (Google) zur automatisierten Prüfung

Wichtig: CSP ist eine zusätzliche Schutzschicht, kein Ersatz

CSP ist „defense in depth“ – es ersetzt nicht:

  • Kontextgerechtes Escaping/Encoding von Nutzereingaben (HTML-, JS-, URL-Kontext)
  • Serverseitige Validierung von Eingaben
  • Sichere Verwendung von APIs wie textContent statt innerHTML

Wenn du magst, kann ich mir eine konkrete CSP für deinen Stack (z. B. React/Angular/klassisches Server-Rendering) genauer ansehen – sag mir einfach, wie eure Inline-Skripte/Styles aktuell eingebunden sind.

<link rel=“stylesheet“ type=“text/css“ media=“all“ href=“https://www.suederlaender.de/Sauerland/wp-content/themes/twentyten/style.css“ />

Dachte 3 s nach

Das sieht nach einem klassischen WordPress-Setup aus (Theme „twentyten“). WordPress bringt für CSP ein paar spezifische Herausforderungen mit sich, hier die wichtigsten Punkte für deinen Fall:

1. style-src für dieses konkrete Stylesheet

Content-Security-Policy: style-src 'self' https://www.suederlaender.de;

Da das Stylesheet von der eigenen Domain kommt, reicht 'self' bereits aus – eine explizite Domain-Nennung ist nur nötig, falls Assets über eine CDN-Subdomain oder externe Domain ausgeliefert werden.

2. WordPress-typische CSP-Fallstricke

  • Inline-Styles: WordPress und viele Plugins/Themes (auch ältere wie „twentyten“) schreiben oft style="..." direkt ins Markup oder generieren <style>-Blöcke inline (z. B. für Custom-Header-Farben, Widgets). Das bricht mit einer strikten style-src 'self'-Policy.
    • Lösung: Entweder Nonces für WP-generierte <style>-Blöcke einbauen (per Filter auf wp_head), oder Hashes für statische Inline-Styles verwenden, oder pragmatisch 'unsafe-inline' nur für style-src (nicht für script-src!) zulassen – das Risiko ist bei CSS deutlich geringer als bei JS, aber CSS-basierte Datenexfiltration ist theoretisch möglich.
  • Inline-Scripts: wp_head()/wp_footer() fügen oft Inline-<script>-Blöcke ein (z. B. für emoji-release.min.js-Konfiguration, Google Fonts, Analytics-Snippets). Hier brauchst du zwingend Nonces oder Hashes.
  • Plugins: jedes aktive Plugin kann zusätzliche externe Domains (Fonts, Skripte, iFrames) einbinden – die CSP muss diese explizit whitelisten oder die Plugins müssen darauf geprüft werden.
  • jQuery & Legacy-Theme-JS: alte Themes wie „twentyten“ nutzen teils eval()-artige Patterns in Legacy-jQuery-Plugins – prüfen, ob 'unsafe-eval' wirklich nötig ist oder ob es umgangen werden kann.

3. Praktisches Vorgehen für WordPress

  1. CSP zunächst als Content-Security-Policy-Report-Only per Plugin (z. B. „Headers Security Advanced & HSTS WP“ oder manuell über .htaccess/functions.php) einspielen
  2. Reports sammeln (report-uri), um zu sehen, welche Domains/Inline-Blöcke tatsächlich gebraucht werden
  3. Erst danach zur echten Content-Security-Policy wechseln

Willst du, dass ich dir eine konkrete Policy für ein WordPress-Setup mit functions.php-Snippet zum Setzen der Header und zum Nonce-Handling in wp_head/wp_footer baue?

mach mal

WordPress CSP-Implementation mit Nonce-Handling

1. functions.php – Header setzen + Nonce generieren

php

<?php
/**
 * CSP mit Nonce-basiertem script-src / style-src
 * In functions.php des aktiven Themes (oder besser: eigenes Must-Use-Plugin)
 */

// Nonce einmal pro Request generieren und global verfügbar machen
function csp_generate_nonce() {
    if ( ! defined('CSP_NONCE') ) {
        define('CSP_NONCE', base64_encode(random_bytes(16)));
    }
    return CSP_NONCE;
}

// CSP-Header setzen (vor jedem Seitenaufruf)
add_action('send_headers', function() {
    $nonce = csp_generate_nonce();

    $csp = "default-src 'self'; " .
           "script-src 'self' 'nonce-{$nonce}' 'strict-dynamic'; " .
           "style-src 'self' 'nonce-{$nonce}'; " .
           "img-src 'self' data: https:; " .
           "font-src 'self' data:; " .
           "object-src 'none'; " .
           "base-uri 'self'; " .
           "form-action 'self'; " .
           "frame-ancestors 'self'; " .
           "report-uri /wp-json/csp/v1/report";

    // Zunächst im Report-Only-Modus testen:
    header("Content-Security-Policy-Report-Only: {$csp}");

    // Nach erfolgreichem Testlauf umstellen auf:
    // header("Content-Security-Policy: {$csp}");
});

2. Nonce auf WordPress-generierte Inline-Scripts anwenden

WordPress fügt an vielen Stellen automatisch Inline-<script>-Blöcke ein (z. B. für wp_add_inline_script). Der Filter script_loader_tag fängt das ab:

php

// Nonce zu allen <script>-Tags hinzufügen
add_filter('script_loader_tag', function($tag, $handle) {
    $nonce = csp_generate_nonce();
    if (strpos($tag, 'nonce=') === false) {
        $tag = str_replace(' src=', " nonce=\"{$nonce}\" src=", $tag);
    }
    return $tag;
}, 10, 2);

// Dasselbe für <style>-Tags
add_filter('style_loader_tag', function($tag, $handle) {
    $nonce = csp_generate_nonce();
    if (strpos($tag, 'nonce=') === false) {
        $tag = str_replace('<link ', "<link nonce=\"{$nonce}\" ", $tag);
    }
    return $tag;
}, 10, 2);

Wichtig: Echte Inline-<script>-Blöcke (nicht <script src="...">), die z. B. per wp_add_inline_script() oder direkt im Theme via echo '<script>...' eingebunden werden, muss man manuell mit dem Nonce versehen:

php

// Beispiel: eigenes Inline-Script im Theme mit Nonce ausstatten
function my_inline_script() {
    $nonce = csp_generate_nonce();
    echo "<script nonce=\"{$nonce}\">console.log('safe inline script');</script>";
}
add_action('wp_footer', 'my_inline_script');

3. Reporting-Endpoint einrichten (REST API)

php

// Registriert /wp-json/csp/v1/report zum Sammeln von Verstößen
add_action('rest_api_init', function() {
    register_rest_route('csp/v1', '/report', [
        'methods' => 'POST',
        'callback' => function(WP_REST_Request $request) {
            $body = $request->get_body();
            $log_file = WP_CONTENT_DIR . '/csp-violations.log';
            file_put_contents(
                $log_file,
                date('c') . ' ' . $body . PHP_EOL,
                FILE_APPEND | LOCK_EX
            );
            return new WP_REST_Response(null, 204);
        },
        'permission_callback' => '__return_true',
    ]);
});

⚠️ In Produktion: Rate-Limiting auf den Endpoint legen (sonst potenzieller DoS-Vektor durch massenhafte Fake-Reports), und Log-Datei nicht öffentlich zugänglich machen (.htaccess-Schutz oder außerhalb des Web-Roots ablegen).

4. Vorgehen in der Praxis

  1. Report-Only aktivieren, ein paar Tage/Wochen normalen Traffic beobachten
  2. csp-violations.log auswerten – welche Domains/Inline-Blöcke schlagen fehl?
    • Häufige Kandidaten bei WP: Google Fonts, Gravatar (secure.gravatar.com), Emoji-Skripte, Analytics/Tracking-Plugins, Elementor/Page-Builder-Inline-Styles
  3. Policy entsprechend ergänzen (Domains whitelisten, weitere Nonce-Stellen identifizieren)
  4. Erst dann auf echten Content-Security-Policy-Header umstellen (Zeile im Code oben austauschen)

5. Falls zu viele Inline-Styles das Nonce-Verfahren unpraktikabel machen

Bei stark Plugin-lastigen WP-Installationen mit vielen dynamischen Inline-Styles ist ein pragmatischer Kompromiss verbreitet:

style-src 'self' 'unsafe-inline';
script-src 'self' 'nonce-{RANDOM}' 'strict-dynamic';

Also 'unsafe-inline' nur für Styles (deutlich geringeres Risiko als bei Scripts), während script-src strikt bleibt. Das ist ein bewusster Trade-off zwischen Sicherheit und Wartungsaufwand – die eigentliche XSS-Schutzwirkung kommt fast ausschließlich aus der script-src-Härtung.


Soll ich dir noch zeigen, wie man das für ein Caching-Plugin (z. B. WP Super Cache, W3 Total Cache) anpasst? Da der Nonce pro Request einzigartig sein muss, verträgt sich das nicht trivial mit vollständig gecachtem HTML – das ist ein häufiger Stolperstein bei WordPress-CSP-Setups.

Komme ggf. später drauf zurück …

AI generate Human verified

Kommentare sind deaktiviert.

ATLsoft | Top