Härtung der Web-Infrastruktur: Dritte Skripte per Content Security Policy (CSP) kontrollieren

Management Summary
Content Security Policy, kurz CSP, legt für eine Website fest, welche Quellen und Programme der Browser laden darf. Die Regeln begrenzen unerwünschten Code und bestimmte Angriffe durch eingeschleuste Inhalte. Das ist besonders bei Websites mit Diensten mehrerer externer Anbieter relevant. Benötigte Funktionen sollen weiter arbeiten, unnötige Zugriffe dagegen eingeschränkt werden. CSP bildet eine zusätzliche Schutzschicht: Ein erlaubtes Programm kann Daten weiterhin missbrauchen, weshalb eine Freigabe keine Vertrauensgarantie ist.
Inhalt
- Was CSP schützt und wo die Grenze liegt
- 1. Tatsächliche Abhängigkeiten erfassen
- 2. Mit einer ausdrücklich nicht blockierenden Richtlinie beginnen
- 3. Einen zuständigen Auslieferungsweg festlegen
- 4. Inline-Skripte mit Nonce oder Hash freigeben
- 5. Meldungen und Funktion getrennt prüfen
- Fragen und Antworten
- Quellen
Was CSP schützt und wo die Grenze liegt
Eine Content Security Policy (CSP) legt für ein Dokument fest, welche Ressourcen der Browser laden und welche Skripte ausführen darf. Bei Cross-Site Scripting wird eingeschleuster Code innerhalb einer Webseite ausgeführt. Eine CSP erschwert bestimmte Angriffe dieser Art. Zugelassener JavaScript-Code behält jedoch Zugriff auf das Dokument. Eine kompromittierte Bibliothek von einer erlaubten Quelle wird nicht allein deshalb erkannt, weil eine CSP existiert.
Escaping wandelt Sonderzeichen so um, dass Text nicht als Code interpretiert wird. Die Eingabeprüfung kontrolliert, ob übermittelte Werte den erwarteten Regeln entsprechen. CSP ergänzt diese Maßnahmen und die Prüfung der verwendeten externen Bibliotheken. Eine Domain-Erlaubnisliste benennt zugelassene Quellserver. Ob jede geladene Datei unverändert und vertrauenswürdig ist, prüft diese Liste nicht. Eine Einwilligung entsteht dadurch ebenfalls nicht. Schädliche Skripte können weiterhin Formulareingaben kopieren (Form-Skimming) oder Daten an erlaubte Ziele senden. Eine Seiten-CSP ist außerdem keine verlässliche Sperre gegen privilegierte Browser-Erweiterungen: Chrome trennt isolierte Content Scripts vom Hauptkontext; im Hauptkontext gilt dagegen die Seitenrichtlinie.

1. Tatsächliche Abhängigkeiten erfassen
Die Bestandsaufnahme umfasst Skripte, Styles, Bilder, Schriften, Frames und Verbindungen nach Interaktionen. Dabei zählen unterschiedliche Seitentypen, Sprache, angemeldeter Zustand, Einwilligungszustand und Formularabläufe. Ein Tag Manager kann zusätzliche Ziele nachladen; dessen Host allein beschreibt nicht alle Abhängigkeiten. Für Matomo Cloud sind die tatsächlichen Instanz-URLs maßgeblich, keine pauschale Freigabe von matomo.cloud.
‘self’ meint den eigenen Origin einschließlich Schema, Host und Port, nicht alle Subdomains und nicht denselben physischen Server. default-src dient als Rückfall für bestimmte Fetch-Direktiven, aber nicht für base-uri, form-action oder frame-ancestors. connect-src betrifft Browserverbindungen wie fetch, XHR, sendBeacon und WebSocket; Weiterleitungen von einem serverseitigen GTM-Container zu Werbeplattformen kontrolliert die Browser-CSP nicht.
2. Mit einer ausdrücklich nicht blockierenden Richtlinie beginnen
Jede Direktive ist eine Regel für eine bestimmte Ressource oder Aktion. Beispielsweise steuert script-src Skripte, form-action die Ziele von Formularübermittlungen und frame-ancestors die Websites, die diese Seite einbetten dürfen. Die folgende Bestandsrichtlinie ist ein Startpunkt für Report-Only auf einer Testumgebung. Inline-Skripte und Inline-Styles stehen direkt im HTML statt in getrennten Dateien. Diese und Drittanbieterressourcen sind bewusst noch nicht freigegeben. In einer typischen WordPress-Installation sind deshalb Meldungen zu erwarten. Ohne Anpassung ist diese Richtlinie keine fertige Produktionskonfiguration.
default-src 'self';
script-src 'self';
style-src 'self';
img-src 'self' data:;
font-src 'self';
connect-src 'self';
frame-src 'none';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'self';
Als Content-Security-Policy-Report-Only meldet die Richtlinie erkannte Verstöße, blockiert aber nicht aufgrund dieser Richtlinie. Eine gleichzeitig vorhandene blockierende CSP bleibt wirksam. Erst nach Prüfung der erforderlichen Ressourcen und Abläufe wird dieselbe abgestimmte Richtlinie als Content-Security-Policy aktiviert. ‘unsafe-inline’ würde Inline-Skripte wieder pauschal erlauben und ist kein Ersatz für eine saubere Freigabe einzelner Skripte.
3. Einen zuständigen Auslieferungsweg festlegen
Ein HTTP-Antwortheader überträgt Angaben oder Anweisungen zusammen mit dem Seiteninhalt. Für eine statische Richtlinie kann Nginx diesen Header setzen. Der folgende Ausschnitt gehört in den passenden vorhandenen server- oder location-Kontext; TLS, Proxy, PHP und bestehende Sicherheitsheader bleiben Teil der vollständigen Konfiguration. Der Header wird mit der Antwort ausgeliefert und anschließend vom Browser ausgewertet. Dadurch wird weder PHP-Ausführung verhindert noch ein Angriff schon vor PHP erkannt.
add_header Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-src 'none'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self';" always;
Vor einem geplanten Reload prüft nginx -t die vollständige Konfiguration. Bei einem Fehler erfolgt kein Reload. Der Beispielausschnitt wurde in einer getrennten Minimal-Konfiguration auf Syntax geprüft; bestehende produktive Serverkonfigurationen wurden damit nicht getestet.
sudo nginx -t && sudo systemctl reload nginx
Die Standardvererbung von add_header entfällt auf einer untergeordneten Ebene, sobald dort eigene add_header-Zeilen stehen. always ändert die berücksichtigten Antwortstatus, nicht diese Vererbungsregel. Ein gemeinsames Include kann die Header in relevanten Kontexten konsistent halten. Ab Nginx 1.29.3 existiert zusätzlich add_header_inherit merge; diese Option erfordert eine passende Version und Prüfung auf ungewollt doppelte Header.
Ohne Zugriff auf Nginx kann ein kleines projektspezifisches WordPress-Plugin den Report-Only-Header auf regulären Frontend-Antworten ergänzen. Das ist ein alternativer Auslieferungsweg, kein automatisch aktivierter Fallback zu Nginx. Das Beispiel ist nicht an WordPress 7.0.2 gebunden und deckt nicht automatisch Admin, Login, REST oder direkt ausgelieferte Cache-Dateien ab.
<?php
/** Plugin Name: Example CSP Report-Only */
add_action('send_headers', static function () {
if (is_admin() || headers_sent()) {
return;
}
$policy = "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-src 'none'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self';";
header('Content-Security-Policy-Report-Only: ' . $policy);
});
Der Hook send_headers kann zusätzliche Antwortheader setzen. Bereits gesendete Ausgabe und vorgeschaltete Caches müssen berücksichtigt werden. Zwei blockierende CSP-Header gelten gleichzeitig; eine Freigabe in nur einer Richtlinie hebt die andere nicht auf. CDN, Reverse Proxy, Webserver und Anwendung sind deshalb auf tatsächlich ausgelieferte Header zu prüfen.
4. Inline-Skripte mit Nonce oder Hash freigeben
Eine Nonce ist ein kryptografisch zufälliger, nicht vorhersagbarer Wert für jede neue HTML-Antwort. Header und ausdrücklich vertrauenswürdige script-Elemente erhalten denselben Wert. Eine WordPress-Aktionsnonce ist dafür nicht geeignet. Ein pauschaler HTML-Filter, der jedes vorhandene Skript nachträglich markiert, könnte auch eingeschleusten Code freigeben. Cache und HTML müssen mit der Nonce-Erzeugung abgestimmt sein.
Das folgende eigenständige PHP-Dokument demonstriert die Zuordnung. Das ist kein vollständiger WordPress-Patch. no-store verhindert hier die beabsichtigte Wiederverwendung einer gespeicherten Antwort. In einer echten Anwendung muss die Nonce gezielt an alle benötigten vertrauenswürdigen Skripte übergeben werden.
<?php
$nonce = base64_encode(random_bytes(16));
header("Content-Security-Policy: default-src 'none'; script-src 'nonce-$nonce'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'");
header('Cache-Control: no-store');
header('Content-Type: text/html; charset=utf-8');
?>
<!doctype html><html lang="en"><meta charset="utf-8">
<title>Nonce example</title><p id="result">Pending</p>
<script nonce="<?= htmlspecialchars($nonce, ENT_QUOTES, 'UTF-8') ?>">
document.getElementById('result').textContent = 'Allowed script ran';
</script></html>
Ein Hash ist ein berechneter Fingerabdruck des Skripttextes. Hashes eignen sich für exakt bekannte Skriptinhalte; schon eine relevante Änderung an Leerraum oder Inhalt verändert den Hash. Inline-Ereignisattribute wie onclick werden durch eine Nonce am script-Element nicht freigegeben. ‘strict-dynamic’ kann Vertrauen an dynamisch geladene Skripte weitergeben und schwächt in unterstützenden Browsern die Rolle klassischer Hostlisten; diese Vertrauensweitergabe gehört ausdrücklich in die Prüfung. Für GTM dokumentiert Google eine Nonce-fähige Einbindung und zusätzliche Direktiven je Tag-Funktion.
5. Meldungen und Funktion getrennt prüfen
Die Browserkonsole ist die Meldungsansicht in den Entwicklerwerkzeugen. Ohne Meldeziel dient diese Ansicht als lokale Prüfquelle. Für einen zuvor eingerichteten HTTPS-Empfänger können Reporting-Endpoints und report-to ergänzt werden; report-uri dient bei Bedarf als älterer Kompatibilitätsweg. Moderne Reports und report-uri verwenden unterschiedliche JSON-Formate. Die Zustellung ist nicht vollständig garantiert: Browserunterstützung, Blocker und Netzfehler begrenzen die Aussagekraft. URL-Daten und gegebenenfalls Skriptausschnitte in Reports benötigen begrenzte Speicherung und vorsichtige Verarbeitung.
Reporting-Endpoints: csp="https://reports.example.com/csp"
Content-Security-Policy-Report-Only: default-src 'self'; report-to csp; report-uri https://reports.example.com/csp-legacy
Die Adressen sind Platzhalter und benötigen echte Empfänger. Nicht jede gemeldete Quelle gehört automatisch auf die Erlaubnisliste. Die Abnahme umfasst Antwortheader auf verschiedenen Dokumenttypen, Weiterleitungen und Fehlerseiten sowie Cache-Treffer, Formularversand, Medien, Einwilligungswechsel und Tracking-Anfragen. HTTP 200 allein beweist weder korrekte Datenerfassung noch die richtige Verarbeitung im Zielsystem.
Fragen und Antworten
Verhindert connect-src jeden Datenabfluss?
Nein. Die Direktive kontrolliert bestimmte Verbindungsarten, nicht sämtliche Ausgabewege oder erlaubte Zielsysteme. Weitere Direktiven betreffen beispielsweise Bilder und Formulare. Bereits zugelassener Code, kompromittierte eigene Endpunkte und fehlerhafte Anwendungslogik bleiben gesonderte Risiken.
Kann Report-Only per meta-Element aktiviert werden?
Nein. Report-Only wird als HTTP-Antwortheader ausgeliefert. Auch eine blockierende CSP per meta-Element unterstützt nicht alle Header-Funktionen, etwa frame-ancestors. Für eine durchgängige Einführung ist der tatsächliche Dokument-Antwortheader maßgeblich.
Quellen
OWASP Top 10 · WordPress: functions.php und Erweiterungspunkte
Verstehen durch Ausprobieren
Welche Skripte lässt diese CSP zu?
Baue eine kleine script-src-Regel. Die Beispielseite zeigt für vier fiktive Skripte, was erlaubt wäre.
Lernmodell einer einzelnen script-src-Direktive. Es lädt oder führt die fiktiven Skripte nicht aus. Ohne strict-dynamic, Hashes, Fallbacks oder mehrere Policies. Ein echter Nonce muss unvorhersehbar und je Antwort neu sein; demo ist nur ein Platzhalter. Erlaubte Dritt-Skripte bleiben vertrauenswürdigkeitsbedürftig.
Dieses Modul rechnet im Browser und sendet oder speichert Deine Eingaben nicht.
Verstehen durch Ausprobieren
Eine Minute: Findest Du den Denkfehler?
script-src 'self' 'nonce-demo' 'unsafe-inline': Darf ein Inline-Skript ohne passenden Nonce in diesem Modell laufen?
In dieser CSP3-Regel wird unsafe-inline wegen der Nonce-Quelle ignoriert. Ein Inline-Skript braucht den passenden Nonce. self allein erlaubt keine Inline-Skripte.
Begriffe zum Ausprobieren
Entdecken
Begriffe zum Ausprobieren
Eine kurze Erklärung und ein kleines Experiment machen den Begriff greifbar.
Nonce
Bei CSP kann ein zufälliger, pro Antwort neuer Wert ein bestimmtes Skript freigeben. Der Nonce am Skript muss zum Wert im Header passen.
Die Übungen rechnen im Browser. Deine Antworten werden vom Lernmodul nicht versendet.