Content-Security-Policy (CSP) von Grund auf aufbauen
OWASP
0x00
Security · Content-Security-Policy · XSS-Schutz · Magento 2
Content-Security-Policy (CSP) von Grund auf aufbauen
von der ersten Direktive bis zum produktionsreifen Rollout

Eine falsch konfigurierte oder komplett fehlende Content Security Policy öffnet Cross Site Scripting Angriffen Tür und Tor, selbst wenn der restliche Code sauber ist. Dieser Artikel zeigt Schritt für Schritt, wie man Direktiven wie script src und frame ancestors korrekt aufbaut, Report Only Modus für einen sicheren Rollout nutzt und die Policy speziell für Magento und Hyvä Storefronts absichert.

18 Min. Lesezeit CSP-Direktiven · Nonce & Hash · Reporting API Magento 2.4.8 · Hyvä Theme · OWASP

1. Was eine Content-Security-Policy wirklich leistet

Eine Content-Security-Policy ist ein HTTP-Antwort-Header, der dem Browser explizit mitteilt, aus welchen Quellen Skripte, Styles, Bilder und weitere Ressourcen geladen werden dürfen. Anders als ein Web-Application-Firewall-Filter arbeitet die CSP nicht auf Basis von Mustern im Traffic, sondern setzt eine Positivliste durch, die der Browser selbst durchsetzt, bevor eine Ressource überhaupt ausgeführt wird. Damit ist die CSP die wirksamste Verteidigungslinie gegen Cross-Site-Scripting, denn selbst wenn ein Angreifer erfolgreich HTML oder JavaScript in eine Seite injiziert, verhindert eine korrekt konfigurierte Policy, dass der Browser dieses Skript überhaupt ausführt oder Daten an eine fremde Domain sendet.

Der Aufbau einer CSP von Grund auf ist jedoch keine reine Kopieraufgabe aus einem Beispiel-Header. Jede Direktive muss zur tatsächlichen Ressourcenlandschaft der Seite passen, sonst brechen Checkout, Kartenintegrationen oder Video-Embeds. Die folgenden Abschnitte bauen eine CSP systematisch auf: von der Direktiv-Syntax über Nonce- und Hash-basiertes Allowlisting bis zum produktionsreifen Rollout mit Report-Only-Modus und Violation Reporting, speziell zugeschnitten auf Magento- und Hyvä-Storefronts.

2. CSP-Direktiven im Detail: default-src, script-src & Co.

Jede CSP besteht aus einer Liste von Direktiven, getrennt durch Semikolon, wobei jede Direktive einen Ressourcentyp und eine Reihe erlaubter Quellen definiert. default-src 'self' ist der Fallback für alle nicht explizit gesetzten Fetch-Direktiven und sollte immer als restriktivste Basis dienen. script-src steuert, woher JavaScript geladen und ausgeführt werden darf, style-src regelt CSS, und img-src bestimmt erlaubte Bildquellen, wobei data: für Data-URIs oft gezielt freigegeben werden muss, etwa für SVG-Icons oder Base64-kodierte Platzhalterbilder.

connect-src ist besonders relevant für moderne Storefronts, weil sie AJAX-Aufrufe, WebSockets und fetch()-Requests kontrolliert, also genau die Kanäle, über die Payment-Provider und Tracking-Skripte Daten austauschen. frame-ancestors ersetzt den veralteten X-Frame-Options-Header und legt fest, welche Seiten die eigene Seite in einem iframe einbetten dürfen, ein zentraler Schutz gegen Clickjacking. Zusätzliche Härtungsdirektiven wie object-src 'none' und base-uri 'self' schließen ältere Angriffsvektoren über Plugins beziehungsweise Base-Tag-Injection.


# .htaccess or vhost config: baseline CSP for a Magento storefront
<IfModule mod_headers.c>
    Header always set Content-Security-Policy "default-src 'self'; \
        script-src 'self' 'nonce-{NONCE}' https://www.googletagmanager.com; \
        style-src 'self' 'unsafe-inline'; \
        img-src 'self' data: https://www.google-analytics.com; \
        connect-src 'self' https://www.google-analytics.com; \
        frame-ancestors 'self'; \
        object-src 'none'; \
        base-uri 'self'; \
        report-to csp-endpoint"
    Header always set Reporting-Endpoints "csp-endpoint=\"https://mironsoft.de/csp/report\""
</IfModule>

3. Nonce- vs. Hash-basiertes Script-Allowlisting

Sobald script-src nicht mehr pauschal 'unsafe-inline' erlaubt, braucht jedes legitime Inline-Skript einen expliziten Freibrief. Zwei Mechanismen stehen dafür bereit: Nonce-basiertes und Hash-basiertes Allowlisting. Bei der Nonce-Methode generiert der Server bei jedem Seitenaufruf einen zufälligen, kryptographisch sicheren Wert, trägt ihn sowohl im CSP-Header als 'nonce-XYZ' als auch als nonce-Attribut am jeweiligen <script>-Tag ein. Der Browser führt nur Skripte aus, deren Nonce-Attribut exakt mit dem im Header übereinstimmt, ein Angreifer kann diesen Wert nicht erraten, weil er sich bei jedem Request ändert.

Hash-basiertes Allowlisting eignet sich für statische Inline-Skripte, deren Inhalt sich nie ändert: Der SHA-256-Hash des exakten Skriptinhalts wird einmalig berechnet und im Header als 'sha256-...' hinterlegt. Der Vorteil gegenüber Nonces liegt darin, dass kein Server-Templating pro Request nötig ist, der Nachteil: Schon ein einziges verändertes Zeichen im Skript, etwa durch Minifizierung oder Whitespace-Änderungen, macht den Hash ungültig und blockiert das Skript.


<!-- Nonce-based: server generates a fresh random value per request -->
<script nonce="r4nd0mBase64Value==">
  window.dataLayer = window.dataLayer || [];
</script>

<!-- Matching CSP header directive -->
<!-- script-src 'self' 'nonce-r4nd0mBase64Value==' -->

<!-- Hash-based: no nonce needed, but the script content must never change -->
<script>
  console.log('static inline script, hashed at build time');
</script>

<!-- Matching CSP header directive, generated once via sha256sum -->
<!-- script-src 'self' 'sha256-B2yPHKaXnvFWtRChIbabYmUBFZdVfKKz1CvmYuXVUJI=' -->

4. unsafe-inline und unsafe-eval: die größten Löcher in der CSP

'unsafe-inline' und 'unsafe-eval' sind die beiden Direktiv-Werte, die eine CSP am effektivsten aushebeln, weil sie genau die Angriffsfläche wieder öffnen, die CSP eigentlich schließen soll. 'unsafe-inline' in script-src erlaubt jedes Inline-Skript unabhängig von Herkunft oder Inhalt, ein per XSS injiziertes <script>-Tag wird dann ganz normal ausgeführt, die Policy bietet in diesem Fall keinerlei Schutz mehr gegen den häufigsten realen Angriffsvektor. Browser ignorieren 'unsafe-inline' ohnehin automatisch, sobald eine Nonce oder ein Hash im selben Direktiv-Wert vorhanden ist, ein nützlicher Fallback-Mechanismus für ältere Browser, der aber nicht mit absichtlichem gleichzeitigem Einsatz beider Werte verwechselt werden darf.

'unsafe-eval' erlaubt eval(), new Function() und ähnliche dynamische Codeausführung aus Strings, ein klassischer Weg, um aus injiziertem Text ausführbaren Code zu machen. Manche ältere JavaScript-Bibliotheken benötigen 'unsafe-eval' intern, weshalb ein Audit der eingebundenen Drittanbieter-Skripte vor der Verschärfung der Policy unverzichtbar ist.

5. Report-Only Modus: CSP risikofrei ausrollen

Eine neue CSP niemals direkt im Enforce-Modus auf eine produktive Seite ausrollen. Der Header Content-Security-Policy-Report-Only verhält sich identisch zur echten Policy, blockiert aber keine einzige Ressource, er protokolliert lediglich, welche Verstöße bei einer erzwungenen Policy aufgetreten wären. Das erlaubt, über Tage oder Wochen echten Nutzerverkehr zu beobachten, seltene Codepfade wie saisonale Kampagnenseiten, A/B-Test-Varianten oder selten genutzte Checkout-Schritte eingeschlossen, ohne dass ein einziger Kunde eine kaputte Seite zu sehen bekommt.

In der Praxis empfiehlt sich ein stufenweises Vorgehen: Zunächst eine bewusst großzügige Policy nur im Report-Only-Modus ausrollen, die eingehenden Reports mehrere Tage sammeln und auswerten, unerwartete, aber legitime Quellen gezielt in die Allowlist aufnehmen, und erst wenn die Report-Rate auf nahezu null gesunken ist, denselben Header ohne -Report-Only-Suffix scharf schalten. Beide Header können auch parallel gesetzt werden, um eine strengere Zukunfts-Policy neben der aktuell erzwungenen zu testen.


# nginx: roll out a new CSP in report-only mode before enforcing it
server {
    listen 443 ssl;
    server_name mironsoft.de;

    # Observe violations without blocking any resource yet
    add_header Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self' 'nonce-$request_id' https://www.googletagmanager.com; report-to csp-endpoint" always;
    add_header Reporting-Endpoints 'csp-endpoint="https://mironsoft.de/csp/report"' always;

    location /csp/report {
        # Forward violation reports to the Magento CSP report controller
        proxy_pass http://magento_upstream;
    }
}

6. Violation Reporting: report-to und die Reporting API

Ohne einen konfigurierten Report-Endpunkt bleiben CSP-Verstöße unsichtbar, der Browser blockiert zwar die Ressource, informiert aber niemanden darüber. Die veraltete Direktive report-uri sendet Verstoßberichte im alten application/csp-report-Format direkt an eine angegebene URL und wird von modernen Browsern zunehmend nur noch als Fallback unterstützt. Der aktuelle Standard ist die Reporting API: Der Header Reporting-Endpoints definiert einen benannten Endpunkt, und die CSP-Direktive report-to verweist auf diesen Namen, Verstöße werden dann im neuen, strukturierten JSON-Format gesammelt und gebündelt versendet, statt jeden einzelnen Verstoß sofort einzeln zu übertragen.

Für Magento-Shops empfiehlt sich ein eigener, leichtgewichtiger Controller unter einer Route wie csp/report, der eingehende Reports validiert, dedupliziert und in ein Monitoring-System wie einen Log-Aggregator oder Sentry weiterleitet. Ohne Deduplizierung kann ein einzelnes blockiertes Skript, das auf jeder Seite eingebunden ist, innerhalb von Minuten tausende identische Reports erzeugen und den Endpunkt überlasten.


{
  "type": "csp-violation",
  "url": "https://mironsoft.de/checkout/cart/",
  "age": 42,
  "body": {
    "documentURL": "https://mironsoft.de/checkout/cart/",
    "referrer": "https://mironsoft.de/",
    "blockedURL": "https://evil-tracker.example/inject.js",
    "effectiveDirective": "script-src-elem",
    "originalPolicy": "default-src 'self'; script-src 'self' 'nonce-r4nd0mBase64Value' https://www.googletagmanager.com; report-to csp-endpoint",
    "disposition": "enforce",
    "statusCode": 200,
    "sourceFile": "https://mironsoft.de/checkout/cart/",
    "lineNumber": 128,
    "columnNumber": 17
  }
}

7. CSP für Magento- und Hyvä-Storefronts aufbauen

Magento liefert mit dem Modul Magento_Csp bereits ein vollständiges Framework zur CSP-Verwaltung mit, inklusive Admin-Konfiguration unter Stores > Configuration > Security > Content Security Policy für einfache Direktiven. Für komplexere, dynamische Anforderungen implementiert man einen eigenen PolicyCollectorInterface, der zur Laufzeit zusätzliche Quellen wie Google-Tag-Manager-Domains oder Zahlungsanbieter-Hosts in die jeweilige Fetch-Policy einträgt, statt Header-Werte statisch in der Konfiguration zu pflegen.

Auf der Hyvä-Seite übernimmt das hyva-themes/magento2-csp-Modul die Nonce-Injektion für Inline-Skripte automatisch. Jede .phtml-Datei, die ein Inline-<script>-Tag ausgibt, muss unmittelbar danach $hyvaCsp->registerInlineScript() aufrufen, damit das Modul den Skriptinhalt hasht oder mit der aktuellen Nonce versieht und automatisch in die zusammengesetzte Policy einträgt. Wer diesen Aufruf vergisst, riskiert entweder ein durch die Policy blockiertes Skript oder, schlimmer, ein Alpine.js-Setup, das im Enforce-Modus lautlos nicht mehr initialisiert. Diese enge Kopplung zwischen Template und Policy-Collector ist der zentrale Unterschied zu klassischen Magento-Themes, in denen Inline-Skripte meist ungeprüft durchgereicht wurden.


<?php

declare(strict_types=1);

namespace Mironsoft\Security\Csp;

use Magento\Csp\Api\PolicyCollectorInterface;
use Magento\Csp\Model\Policy\FetchPolicy;
use Magento\Framework\App\Area;
use Magento\Framework\App\State;

/**
 * Adds required third party hosts to the storefront script-src directive.
 */
class ThirdPartyPolicyCollector implements PolicyCollectorInterface
{
    /**
     * @param State $appState Application state used to detect the current area.
     */
    public function __construct(private readonly State $appState)
    {
    }

    /**
     * Extends the collected CSP policies with third party script sources.
     *
     * @param array $policies
     * @param bool $isReportOnly
     * @return array
     */
    public function collect(array $policies, bool $isReportOnly = false): array
    {
        if ($this->appState->getAreaCode() !== Area::AREA_FRONTEND) {
            return $policies;
        }

        // Allow Google Tag Manager and Google Analytics on storefront pages only
        $policies[] = new FetchPolicy(
            'script-src',
            false,
            ['https://www.googletagmanager.com', 'https://www.google-analytics.com'],
            [],
            false,
            false,
            false,
            [],
            true
        );

        return $policies;
    }
}

8. Drittanbieter-Skripte in der CSP integrieren

Praktisch jeder Magento-Shop bindet Google Analytics oder Google Tag Manager, einen oder mehrere Zahlungsanbieter sowie ein Chat-Widget ein, und jedes dieser Systeme benötigt eigene Einträge in mehreren Direktiven gleichzeitig. Google Tag Manager braucht typischerweise script-src https://www.googletagmanager.com und connect-src https://www.google-analytics.com, oft ergänzt um img-src für Tracking-Pixel. Zahlungsanbieter wie Adyen, Braintree oder Klarna laden häufig ihr eigenes iframe-basiertes Hosted-Fields-Widget, was zusätzlich frame-src-Einträge für die jeweilige Payment-Domain erfordert, nicht nur script-src.

Der sicherste Ansatz ist, für jeden Drittanbieter-Dienst dessen offizielle CSP-Dokumentation zu prüfen, statt Domains durch Trial-and-Error aus der Browser-Konsole abzuschreiben, weil Anbieter ihre Subdomains und CDN-Hosts regelmäßig ändern. Chat-Widgets wie Zendesk oder Intercom benötigen zusätzlich häufig worker-src für Service-Worker-basierte Push-Benachrichtigungen. Eine zentrale Liste aller freigegebenen Drittanbieter-Domains, gepflegt als eigener PolicyCollectorInterface pro Anbieter, hält die Policy wartbar, statt einen monolithischen Header manuell zu erweitern.

9. CSP-Fehlkonfigurationen im Vergleich

Die häufigsten CSP-Fehlkonfigurationen entstehen nicht durch fehlendes Wissen über einzelne Direktiven, sondern durch das Fehlen einer systematischen Testroutine vor dem Rollout. Ein produktiver Checkout, der plötzlich keine Zahlungsdaten mehr senden kann, eine Google-Maps-Karte, die als leeres graues Feld erscheint, oder ein YouTube-Video-Embed, das nicht startet, sind die drei häufigsten sichtbaren Symptome einer zu restriktiven Policy, meist verursacht durch fehlende frame-src- oder connect-src-Einträge.

Die folgende Übersicht vergleicht typische Fehlkonfigurationen mit der empfohlenen, sicheren Alternative. Zum Testen und Auditieren eignen sich der Google CSP Evaluator, die Chrome-DevTools-Konsole, die bei jedem Verstoß eine detaillierte Meldung mit betroffener Direktive und blockierter URL ausgibt, sowie ein automatisierter Lighthouse-Check in der CI-Pipeline, der eine fehlende oder zu offene CSP als Sicherheitswarnung markiert, bevor ein Deployment überhaupt live geht.

Direktive / Szenario Unsicher / Fehlkonfiguriert Empfohlen / Sicher Warum
script-src 'unsafe-inline' 'self' 'nonce-...' Verhindert Ausführung injizierter Inline-Skripte
object-src nicht gesetzt 'none' Schließt Plugin-basierte XSS-Vektoren
frame-ancestors fehlt komplett 'self' Schutz vor Clickjacking
base-uri nicht gesetzt 'self' Verhindert Base-Tag-Injection
Violation Reporting kein report-to konfiguriert report-to + Reporting-Endpoints Verstöße werden erst sichtbar und auswertbar

In der Praxis zeigt sich immer wieder derselbe Zusammenhang: Jede der oben genannten Fehlkonfigurationen lässt sich vermeiden, wenn eine neue Policy zunächst im Report-Only-Modus gegen echten Traffic getestet wird, bevor sie erzwungen wird. Wer diesen Prozess bei jedem größeren Feature-Release wiederholt, verhindert zuverlässig, dass eine verschärfte CSP unbemerkt den Checkout oder eine Kartenintegration lahmlegt.

Mironsoft

Web-Security, CSP-Audits und Hyvä-Hardening für Magento-Shops

Content-Security-Policy professionell aufbauen und ausrollen?

Wir analysieren eure bestehende Ressourcenlandschaft, bauen eine passgenaue CSP mit Nonce-basiertem Script-Allowlisting auf und rollen sie über Report-Only-Modus risikofrei aus, inklusive Violation-Reporting und Hyvä-spezifischer Integration.

CSP-Audit

Bestehende Policy und fehlende Header analysieren, unsafe-inline-Lücken identifizieren

Rollout-Begleitung

Report-Only-Phase auswerten, Drittanbieter-Domains einpflegen, Report-Endpunkt aufsetzen

Hyvä-Integration

PolicyCollectorInterface implementieren und registerInlineScript() in allen Templates verankern

10. Zusammenfassung

Eine Content-Security-Policy von Grund auf aufzubauen bedeutet, jede Direktive bewusst aus der tatsächlichen Ressourcenlandschaft der Seite abzuleiten, statt eine generische Vorlage zu kopieren. default-src 'self' als restriktive Basis, gezielt ergänzte script-src-, style-src-, img-src- und connect-src-Werte sowie frame-ancestors gegen Clickjacking bilden das Fundament. Nonce-basiertes Allowlisting ersetzt 'unsafe-inline' für dynamisch gerenderte Inline-Skripte, Hash-basiertes Allowlisting eignet sich für statischen Inline-Code, der sich nie ändert.

Der Rollout entscheidet über Erfolg oder Misserfolg: Content-Security-Policy-Report-Only sammelt über Tage echte Verstoßdaten, bevor eine Policy scharf geschaltet wird, report-to und die Reporting API liefern strukturierte, dauerhafte Sichtbarkeit über eingehende Verstöße. In Magento- und Hyvä-Shops übernehmen Magento_Csp und das registerInlineScript()-Pattern die technische Umsetzung, wer diese Werkzeuge konsequent nutzt, schützt den Checkout gegen Cross-Site-Scripting, ohne Kartenintegrationen oder Zahlungs-Widgets zu brechen.

Content-Security-Policy von Grund auf aufbauen: Das Wichtigste auf einen Blick

Direktiven zuerst

default-src 'self' als Basis, script-src, style-src, img-src, connect-src und frame-ancestors gezielt verfeinern.

Nonce statt unsafe-inline

Pro Request generierte Nonce ersetzt Wildcard-Erlaubnisse, Hash-basiertes Allowlisting für statischen Inline-Code.

Report-Only vor Enforce

Neue Policy zuerst beobachten, Verstöße über report-to und die Reporting API sammeln, dann erzwingen.

Magento & Hyvä

Magento_Csp PolicyCollectorInterface und $hyvaCsp->registerInlineScript() statt manuellem Header-Editing.

11. FAQ: Content-Security-Policy (CSP)

1Was ist eine Content-Security-Policy und wofür wird sie gebraucht?
Ein HTTP-Header mit einer Positivliste erlaubter Quellen für Skripte, Styles, Bilder und weitere Ressourcen. Wirksamste Verteidigungslinie gegen Cross-Site-Scripting, auch wenn Output-Encoding versagt.
2Was bewirkt die Direktive default-src?
Fallback für alle nicht explizit gesetzten Fetch-Direktiven. Ein restriktives default-src 'self' lädt nicht spezifizierte Ressourcentypen automatisch nur von der eigenen Domain.
3Nonce- vs. Hash-basiertes Script-Allowlisting?
Nonce: pro Request neuer Zufallswert, für dynamische Inline-Skripte. Hash: einmalig aus dem Skriptinhalt berechnet, nur für statischen, unveränderlichen Code geeignet.
4Warum ist unsafe-inline gefährlich?
Erlaubt jedes Inline-Skript unabhängig von Herkunft. Ein injiziertes script-Tag wird normal ausgeführt, die CSP schützt dann nicht mehr gegen den häufigsten Angriffsvektor.
5Was macht der Report-Only Modus?
Protokolliert Verstöße ohne zu blockieren. Erlaubt tagelanges Testen einer neuen Policy gegen echten Traffic, bevor sie erzwungen wird.
6report-to vs. report-uri?
report-uri ist veraltet und sendet direkt an eine URL. report-to nutzt die Reporting API mit benanntem Endpunkt und liefert Reports gebündelt im neuen JSON-Format.
7CSP für Magento/Hyvä aufbauen?
Magento_Csp mit eigenem PolicyCollectorInterface für dynamische Quellen, ergänzt durch das hyva-themes/magento2-csp-Modul für die automatische Nonce-Injektion in Templates.
8Was ist $hyvaCsp->registerInlineScript()?
Pflichtaufruf nach jedem Inline-script-Tag in phtml-Dateien, damit das Hyvä-CSP-Modul den Inhalt hasht oder mit der Nonce versieht. Ohne den Aufruf blockiert die Policy das Skript.
9Typische Fehler bei Checkout und Maps?
Fehlende connect-src-Einträge brechen Payment-APIs, fehlende frame-src- oder script-src-Einträge führen zu leeren Maps-Feldern und nicht startenden Video-Embeds.
10Wie testet und auditiert man eine CSP?
Google CSP Evaluator für strukturelle Schwächen, Chrome-DevTools-Konsole für Verstoßmeldungen, automatisierter Lighthouse-Check in der CI-Pipeline vor jedem Deployment.