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.
Inhaltsverzeichnis
- 1. Was eine Content-Security-Policy wirklich leistet
- 2. CSP-Direktiven im Detail: default-src, script-src & Co.
- 3. Nonce- vs. Hash-basiertes Script-Allowlisting
- 4. unsafe-inline und unsafe-eval: die größten Löcher in der CSP
- 5. Report-Only Modus: CSP risikofrei ausrollen
- 6. Violation Reporting: report-to und die Reporting API
- 7. CSP für Magento- und Hyvä-Storefronts aufbauen
- 8. Drittanbieter-Skripte in der CSP integrieren
- 9. CSP-Fehlkonfigurationen im Vergleich
- 10. Zusammenfassung
- 11. FAQ
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.