Mit diesem Artikel wird deine Webseite (nicht explizit WordPress) sicherer! Sicherer für deine Nutzer und für dich, da potenzielle Angriffsflächen eliminiert werden! Allerdings ist es ein kompliziertes Thema, also sei stark. Wir erklären es dir möglichst einfach und so praktisch, wie nur möglich. Also so, dass du sofort an die Umsetzung gehen kannst.
Und hierzu starten wir am besten auf webbkoll.dataskydd.net und du erzeugst dir einen Ist-Zustand deiner Webseite. Folgende Bilder zeigen dir mögliche Ergebnisse.


Keine Zeit? Die Kurzfassung zur sicheren Website
Du willst dir nicht alles durchlesen und deine Seite trotzdem sicherer machen? Füge diese Zeilen ganz oben in die Datei .htaccess im Hauptverzeichnis deiner Website ein, außerhalb des Blocks # BEGIN WordPress … # END WordPress:
Header always set Strict-Transport-Security "max-age=300; includeSubDomains" env=HTTPS Header always set X-Frame-Options "SAMEORIGIN" Header always set X-Content-Type-Options "nosniff" Header always set Referrer-Policy "strict-origin-when-cross-origin" Header always set Permissions-Policy "geolocation=(), camera=(), microphone=()"
Danach rufst du deine Seite auf und prüfst, ob alles funktioniert, vor allem Formulare, eingebettete Videos und Karten. Wenn nicht, entfernst du die Zeilen wieder.
Gut zu wissen:
- Die Content-Security-Policy fehlt hier bewusst. Sie muss zu deiner Seite passen und lässt sich nicht pauschal kopieren.
- Das funktioniert auf Servern mit Apache bzw. kompatiblen Servern wie LiteSpeed (bei den meisten Hostern der Fall). Bei nginx oder einem CDN wie Cloudflare stellst du die Header dort ein.
- Die HSTS-Zeile startet mit fünf Minuten. Wie du die Laufzeit gefahrlos erhöhst, steht im Abschnitt HSTS.
HSTS und Referrer Policy einstellen
Da ich dir alle Einstellungen anhand des oben erwähnten Tools näher bringen möchte – damit du schnell einen visuellen Erfolg siehst und Spaß an den folgenden Arbeiten hast – möchte ich dir zunächst erklären, was Webkoll ist.
Das Projekt wird von Internetfonden und IIS aus Schweden finanziert1. Entwickelt wurde das Tool von dataskydd.net, wodurch sich die URL erklärt. Der Quellcode ist auf Github2 einsehbar und Open-Source.
Das Tool simuliert einen Nutzer, der deine Seite im Browser aufruft, sammelt verschiedene Informationen und stellt dir diese Ergebnisse halbwegs vernünftig dar. Zwar gibt es sogar weitreichende Erklärungen, aber diese sind nicht trivial zu verstehen.
via .htaccess
- Wenn du nun folgende Angaben in die ersten Zeilen deiner .htaccess Datei integrierst, sollte der obige Test schon deutlich besser ausfallen.3
Selbstverständlich ist bei allen Arbeiten an deiner Webseite ein Backup sinnvoll. Bei den meisten Zeilen ist das Schlimmste, was passieren kann, dass deine Website nicht mehr richtig lädt : P Sobald du sie wieder entfernst, ist alles wie vorher. Eine Ausnahme ist HSTS: Diese Regel merkt sich der Browser für die eingestellte Laufzeit, auch wenn du die Zeile löschst. Deshalb starten wir unten bewusst mit nur fünf Minuten.
<IfModule mod_headers.c>
Header always set Strict-Transport-Security "max-age=300; includeSubDomains" env=HTTPS
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
</IfModule>
Mittels „;“ erstellst du mehrere Anweisungen, die einzelnen Werte müssen sich zwischen zwei „“ befinden.
Update Oktober 2026: X-XSS-Protection entfernt
In früheren Versionen dieses Artikels stand hier X-XSS-Protection "1; mode=block". Diese Zeile empfehlen wir nicht mehr. Der Header steuerte einen eingebauten XSS-Filter, den es in aktuellen Browsern nicht mehr gibt: Chrome und Edge haben ihn entfernt, Firefox hatte nie einen. Laut MDN4 kann der Header in manchen Fällen sogar Sicherheitslücken erst erzeugen.
Du hast die Zeile schon in deiner .htaccess? Lösch sie einfach. Wenn dein Prüftool sie unbedingt sehen will, setz sie auf 0, so empfiehlt es auch OWASP5. Echten Schutz vor eingeschleusten Skripten bietet die Content-Security-Policy weiter unten.
Nachfolgend können wir uns um die Theorie hinter den Punkten kümmern, aber wir haben deine Webseite bereits deutlich sicherer gemacht! Du solltest nun zwei grüne Haken sehen, denn wir haben HTTPS by default eingestellt und eine Referrer-Policy eingerichtet.
Hinweis: Manche Prüftools, etwa das HTTP Observatory von MDN, werten HSTS erst ab einer Laufzeit von sechs Monaten als bestanden. Mit max-age=300 ist ein fehlender Haken dort also normal, bis du die Laufzeit erhöht hast.
via PHP
Alle Header-Angaben lassen sich auch über die PHP header() Funktion implementieren. Die Funktion arbeitet nach dem Prinzip: Header-Name: Wert. Nachdem du weißt, welche Angaben du dem Browser erklären möchtest, schreibst du Folgendes:
<?php
header('Strict-Transport-Security: max-age=300; includeSubDomains');
header('X-Frame-Options: SAMEORIGIN');
header('X-Content-Type-Options: nosniff');
header('Referrer-Policy: strict-origin-when-cross-origin');
header("Content-Security-Policy-Report-Only: default-src 'self'");
header() muss aufgerufen werden, bevor irgendeine Ausgabe erfolgt, und wirkt nur auf Seiten, die PHP erzeugt, nicht auf Bilder, CSS- oder JS-Dateien. In einer functions.php lässt du das schließende ?> weg.
HSTS – HTTP Strict Transport Security erklärt
Mit der HSTS-Funktion teilt deine Webseite dem aufrufenden Browser mit, dass diese über eine verschlüsselte Verbindung erreichbar ist und dies zwischengespeichert werden soll. Deine Webseite wird somit immer mittels HTTPS abgerufen. Viele Hoster bieten in ihren Einstellungen eine entsprechende Funktion an.

Eine Hoster unabhängige Lösung erreichst du über eine Direktive in der .htaccess Datei. Dann musst du allerdings die Einstellung beim Hoster deaktivieren!
Header always set Strict-Transport-Security "max-age=300; includeSubDomains" env=HTTPS
- Deine Webseite benötigt ein SSL-Zertifikat (davon gehe ich folgend aus)
- Die Anweisung befindet sich in der ersten Zeile deiner .htaccess
- Header set – startet die Anweisung
- Strict-Transport-Security – gibt Auskunft über die Art der Direktive
- max-age=300 – die Dauer in Sekunden, die sich der Browser die HSTS-Regel merkt. Wir starten mit fünf Minuten und erhöhen schrittweise (siehe unten). Eine Obergrenze gibt es nicht; für die Preload-Liste sind mindestens 31536000 Sekunden (ein Jahr) nötig.
- includeSubDomains – die Regel gilt auch für alle Subdomains. Prüfe vorher, ob wirklich
- env=HTTPS – stellt sicher, dass der Header nur gesendet wird, wenn deine Seite via HTTPS ausgeliefert wird
- includeSubDomains – integriert Subdomains
- preload – fehlt im Beispiel bewusst. Damit beantragst du die Aufnahme in die Preload-Liste der Browser. Erst nach der Aufnahme lädt der Browser deine Seite schon beim allerersten Aufruf per HTTPS. Voraussetzung:
max-agevon mindestens einem Jahr undincludeSubDomains.6
Update: HSTS nicht mehr gleich mit einem Jahr und preload
Früher haben wir hier direkt max-age=31536000; includeSubDomains; preload empfohlen und geschrieben, dass beim Entfernen der Zeilen nichts passiert. Für HSTS stimmt das leider nicht: Der Browser merkt sich die Regel für die volle Laufzeit, auch wenn die Zeile längst wieder gelöscht ist. Mit preload kann deine Domain sogar fest in die Browser eingebaut werden, und eine Streichung dauert Monate.
Starte deshalb mit max-age=300 und erhöhe die Laufzeit schrittweise. preload setzt du nur, wenn wirklich jede Subdomain dauerhaft per HTTPS erreichbar ist.
Du hast die alte Zeile schon im Einsatz? Das ist kein Problem, solange HTTPS überall funktioniert. Lass HTTPS auf keinen Fall abschalten. Willst du zurück, setz max-age=0 (wirkt nur über HTTPS) und beantrage gegebenenfalls die Streichung auf hstspreload.org.
Laufzeit von max-age schrittweise anpassen
Auf jeder Stufe solltest du die volle Laufzeit abwarten und prüfen, ob alle Subdomains per HTTPS funktionieren:
max-age=300; includeSubDomains: 5 Minutenmax-age=604800; includeSubDomains: 1 Wochemax-age=2592000; includeSubDomains: 1 Monatmax-age=31536000; includeSubDomains: 1 Jahr- Erst jetzt und nur wenn jede Subdomain dauerhaft HTTPS kann:
max-age=31536000; includeSubDomains; preload(oder zwei Jahre,63072000, wie OWASP empfiehlt) und die Domain auf hstspreload.org einreichen.
Referrer Policy erklärt
Hiermit steuerst du, welche Informationen von deiner Webseite weitergegeben werden. Der Referrer ist die URL, über die ein Besucher zur aktuellen URL gelangt ist. Die Übermittlung geschieht mittels Referrer-HTTP-Header. Zwar ist die Übertragung optional, aber die Webbrowser übertragen ihn standardmäßig. Für eine (interne) Auswertung ist das toll, denn du erfährst von welcher Unterseite die Nutzer auf die handlungsauslösende Seite gelangt sind. Webseitübergreifend ergeben sich durch diese Technik einige Datenschutzrisiken.
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Mit folgenden Direktiven kannst du das Verhalten steuern:
- „“ – der Browser darf selbst entscheiden
- no-referrer – Der Webbrowser soll keinen Referrer-Header senden, keine Referrer-Informationen werden übergeben
- no-referrer-when-downgrade – gilt als Standardeinstellung, wird von HTTPS auf HTTP navigiert, wird kein Referrer übermittelt
- same-origin – Referrer wird nur innerhalb deiner Webseite übertragen, genau genommen bei Same-Origin-Regeln
- origin – nur die Herkunft der Hauptquelle (hier der Domain) wird weitergereicht, Pfadangaben entsprechend nicht
- strict-origin – nur die Herkunft der Hauptquelle (hier der Domain) wird weitergereicht, Pfadangaben entsprechend nicht, solange die Ziele im HTTPS-Raum stattfinden
- origin-when-cross-origin – Als Referrer-Information sendet der Browser innerhalb deiner Domain den kompletten Pfad, nach Außen wird lediglich die Domain übertragen
- strict-origin-when-cross-origin – Die Übertragung eines Referrers geschieht nur, wenn HTTPs gegeben ist: Innerhalb der Origin wird der komplette Pfad übertragen, nach Außen nur die Domain
- unsafe-url – Referrer wird immer gesendet
- no-referrer-when-downgrade – war früher der Standard der Browser. Beim Wechsel von HTTPS auf HTTP wird kein Referrer übermittelt, sonst immer die vollständige URL.
- strict-origin-when-cross-origin – heutiger Standard aller großen Browser: innerhalb deiner Seite die komplette URL, an fremde Seiten nur die Domain, beim Wechsel zu HTTP gar nichts.
- unsafe-url – die vollständige URL samt Parametern wird immer gesendet, auch an HTTP-Seiten. Nicht verwenden.
Stellt eure Regel so restriktiv ein, wie es eure Seite zulässt. strict-origin-when-cross-origin ist ein guter Standard. no-referrer oder same-origin sind strenger, können aber eigene Auswertungen und manche eingebundene Dienste (z. B. Zahlungsanbieter) stören.
Permissions Policy
Taucht oben in der Grafik noch nicht auf und soll den Einstieg in das nächste aufregende Kapitel der Content-Security-Policy sanft einleiten. Browser verfügen über allerhand Schnittstellen, damit sie zum multifunktionalen Tool werden. So kann Ihnen der Zugriff auf die Kamera und das Mikrofon gewährt werden, damit du eine Videokonferenz abhalten kannst. Mittels Cross-Site-Scripting (XSS) werden diese Lücken zweckentfremdet und missbraucht. Also ist es sinnvoll, wenn du deinem Browser gezielt Funktionen freigibst. Mittels Permissions Policy ist dies möglich.
Header always set Permissions-Policy "geolocation=(self \"https://deinwp.de\"), camera=(), fullscreen=*"
Update: Aus der Liste haben wir interest-cohort, vr und speaker entfernt. Die Funktionen dahinter gibt es nicht mehr (FLoC hat Google eingestellt) oder sie waren nie standardisiert, und der Browser meldet sie nur als Warnung in der Konsole.
Erklärungen und eine Übersicht wichtiger Befehle sowie eine ausführliche Anleitung zur Permission-Policy findet sich auf Github.
- Der Befehl ‚self‘ erteilt der aktuellen Webseite die Freigabe
- Wird eine Domain angegeben, „geolocation=(„https://deinwp.de“) erhält sie eine Freigabe.
- Mit einem Stern ‚*‘ wird allen Quellen eine Freigabe ermöglicht.
- Lässt du den Eintrag leer ‚()‘ deaktivierst du die Funktion grundsätzlich.
Eine Art Minimalheader-Angabe würde wie folgt lauten:
Header set Permissions-Policy: "geolocation=(self "https://deinwp.de"), camera=(), fullscreen=*"
Im oberen Beispiel wird folgende Richtlinie definiert: Die Geolocation-Berechtigung gilt für die eigene Website sowie die Domain ‚deinwp.de‘. Keine Webseite darf auf die Kamera zugreifen und den Vollbildmodus darf von jeder Quelle aufgerufen werden.
Du nutzt nginx, Netlify oder Cloudflare statt Apache? Fertige Konfigurationen dafür und eine Versuchsreihe, welche Header per Meta-Tag überhaupt wirken, findest du bei cyberscale.
CSP – Content-Security-Policy
Jetzt wird es wirklich spannend, denn gemeinsam definieren wir, aus welcher Quelle dein Browser seine Daten beziehen darf!
–> Die Content-Security-Policy (CSP Header) legt fest, welche Ressourcen dein Browser laden und ausführen darf.
Jetzt müssen wir die Informationsvermittlung etwas umdrehen und mit der Theorie starten. Zumindest wäre dies sinnvoll, da die folgenden Einstellungen individuell geschehen müssen. Im Praxis-Abschnitt allerdings, versuchen wir uns gemeinsam der Thematik noch aus einer anderen Liste zu nähern!

Oben hast du ein Tool kennengelernt, mit dem du mehrere Dinge testest. Drei grüne Haken stellen hier das Ultimo dar. Allerdings ist auch ein „implementiert, aber mit Fehlern“ besser, als gar keine Content Security anzugeben. Dieses „mit Fehlern“ besagt lediglich, dass einige Richtlinien zu weit gefasst wurden, dadurch im „Widerspruch“ zur Sicherheitsregel steht.
Mit dem Mozilla Observatory steigst du noch tiefer in die Thematik ein. Hier bekommst du den Gamification-Faktor, denn du kannst Punkte und eine Note erreichen.

Das Sicherheitskonzept soll das Einschleusen von Daten in deine Webseite verhindern, also sogenanntes Cross-Site-Scripting unterbinden. Die meisten aktiven Elemente auf Webseiten werden über JavaScript-Code realisiert. Der Webbrowser führt jeden Code aus, der in deiner Seite steht oder von ihr eingebunden wird. Ob er von dir stammt oder eingeschleust wurde, kann er nicht unterscheiden. Mit der Content-Security-Policy legst du fest, aus welchen Quellen Skripte und andere Inhalte kommen dürfen; alles andere blockiert der Browser.
Damit beispielsweise der Customizer geladen wird, müssen die Angaben ‚unsafe-inline‘ ‚unsafe-eval‘ erfolgen im Bereich script-src erfolgen, was nicht ideal ist. Also deaktivierst du die CSP-Regeln im Adminbereich (/wp-admin). Hierzu fügst du innerhalb von /wp-admin/ eine weitere .htaccess ein:
<IfModule mod_headers.c>
Header always unset Content-Security-Policy
Header always unset Content-Security-Policy-Report-Only
</IfModule>
Teste ruhig den Code auf deiner Webseite. Funktioniert etwas nicht, kannst du diese Zeile mit „#“ auskommentieren, wie als wäre nichts geschehen ; ) Teste den Header und springe beispielsweise in den Praxis-Teil.
Ein relativ strikter Header für deine WordPress-Webseite könnte wie folgt aussehen.
Header always set Content-Security-Policy-Report-Only "frame-ancestors 'self'; base-uri 'self'; default-src 'none'; form-action 'self'; connect-src 'self'; img-src 'self' https://secure.gravatar.com https://s.w.org https://wordpress.org https://ps.w.org data:; font-src 'self' data:; object-src 'none'; script-src 'self'; style-src 'self'"
Wir setzen den Header zunächst als Content-Security-Policy-Report-Only. Dann blockiert der Browser nichts, sondern meldet Verstöße nur in der Konsole. Erst wenn dort nichts Wichtiges mehr auftaucht, benennst du ihn in Content-Security-Policy um.
Damit werden allerdings auch Gravatar-Bilder und Icons geladen, was im Abschnitt „img-src“ zu sehen ist. Die folgenden URLs werden explizit erlaubt, was datenschutzrechtlich relevant ist. Demzufolge in deiner Erklärung erwähnt werden muss!
Achtung bei mehreren Zeilen: Mit Header set überschreibt die zweite Zeile die erste, im folgenden Beispiel gilt also nur frame-ancestors 'self':
Header always set Content-Security-Policy "script-src 'self'"<br>Header always set Content-Security-Policy "frame-ancestors 'self'"
Mit Header add entstehen zwei getrennte Policies und der Browser setzt beide durch. Es gilt dann immer die Strengere. Schreib deshalb alle Direktiven in eine Zeile, getrennt durch Semikolon:
Header always set Content-Security-Policy "script-src 'self'; frame-ancestors 'self'"
Theorie: So legst du die CSP an
- Teste deine angegebenen Regeln mit dem CSP-Evaluator
- Nach dem Einbinden prüfst du die Browser-Console auf Fehler
- Teste die Funktionen deiner Webseite
Folgend erfährst du etwas über den Aufbau und die Verwendung der CSPs.
- mit default-src ’none‘; blockierst du zunächst alle Ressourcen und gibst dann Einzelne frei
- Quellen gibst du möglichst exakt an
- Die Angabe von Wildcards und kompletten Domains vermeidest du
Der Aufbau des Headers
Header: „Anweisung Direktive ‚Angabe‘ Quelle;“
- Header set Content-Security-Policy – Wir sagen dem Browser, welche Anweisung als nächstes folgt
- „ – Die Anweisung startet (und endet) mit einem Anführungszeichen
- default-src – Du nennst die Direktive, die du steuern möchtest
- ’none‘ – Deine jeweilige Angabe wird in Apostrophen eingehüllt
- deinwp.de – Du kannst eine Quelle als Adresse, Art oder via Wildcard angeben
- ; – Direktive wird beendet
- „ – Deine Anweisung wird beendet
Beispielsweise: Header set Content-Security-Policy: "default-src 'none'" oder Header set Content-Security-Policy: "script-src 'self' deinwp.de"
Update: Hier standen früher zusätzlich X-Webkit-CSP und X-Content-Security-Policy. Diese alten Varianten braucht seit Jahren kein aktueller Browser mehr. Du kannst sie aus deiner Konfiguration löschen, schaden tun sie aber nicht.
CSP Inhaltstypen (Direktiven)
Folgende Einstellungen lassen sich vornehmen bzw. legen die entsprechenden Inhalte fest. Über „default-src“ kannst du eine standardisierte Direktive festlegen. Musst im Anschluss nur gesonderte Angaben hinzufügen.
- base-uri: Beschränkt die URLs, die im <base>-Element der Webpage auftauchen dürfen.
- child-src: Aus welchen Quellen dürfen Frames und Web Worker geladen werden? Heute nutzt man dafür meist
frame-srcbzw.worker-src. - connect-src: Zu welchen Adressen darf deine Seite per JavaScript Verbindungen aufbauen (fetch, AJAX, WebSockets)? Wichtig z. B. für AJAX-Formulare und die REST-API.“ Die Angabe „Welche Links sind zulässig?“ ist falsch.
- font-src: Von welchen Quellen dürfen Schriftarten geladen werden? (Hier müsstest du beispielsweise Google-Fonts eintragen)
- form-action: Welche Endpunkte kann ein Formular besitzen?
- frame-ancestors: Dürfen andere Domains deine Webseite in Frames und iFrames einbauen?
- img-src: Aus welchen Quellen dürfen Bilder geladen werden? (Gravatar und Co. müssten hier explizit erlaubt werden)
- media-src: Aus welchen Quellen dürfen Audio- und Video-Formate geladen werden?
- object-src: Aus welchen Quellen dürfen
<object>– und<embed>-Elemente laden? Empfehlung:'none'. plugin-types: Und welche Arten von Erweiterungen lässt du zu?(Direktive wurde aus den Browsern entfernt)- report-uri: An welche URL soll ein Bericht gesendet werden, wenn gegen deine Regeln verstoßen wurde? Gilt als veraltet, wird aber noch von allen Browsern unterstützt.
- report-to: der Nachfolger von report-uri. Statt einer URL steht hier ein Name, dessen Adresse du im Header
Reporting-Endpointsfestlegst. Noch nicht jeder Browser kennt report-to, deshalb gibst du vorerst beide an. - script-src: Welche Quellen sind für JavaScript erlaubt?
- style-src: Welche Quellen sind für Stylesheets erlaubt?
- upgrade-insecure-requests: Der Browser lädt alle per
http://eingebundenen Inhalte deiner Seite automatisch überhttps://. - sandbox: Die entsprechende Seite wird in eine Sandboxverschoben. Hier sind Dinge wie Formulare, Pop-ups und das Ausführen von Skripten verboten
Verstöße melden lassen: report-uri und report-to
Im Berichtsmodus (Content-Security-Policy-Report-Only) blockiert der Browser nichts, sondern zeigt Verstöße nur in seiner eigenen Konsole an. Das reicht, solange du selbst testest. Was bei deinen Besuchern passiert, etwa mit anderen Browsern, Plugins oder auf Unterseiten, die du nicht aufgerufen hast, siehst du so nicht.
Dafür gibt es die Berichtsfunktion: Der Browser schickt jeden Verstoß als kleine Nachricht an eine Adresse, die du festlegst. Dafür gibt es zwei Wege:
- report-uri nennt die Adresse direkt in der CSP. Das ist der ältere Weg, den aber alle Browser verstehen.
- report-to nennt nur einen Namen. Die Adresse dazu steht im zusätzlichen Header
Reporting-Endpoints. Das ist der neue Weg, den noch nicht jeder Browser kennt.
Deshalb gibst du vorerst beide an. Browser, die report-to kennen, ignorieren dann report-uri. Am Beispiel einer schlichten Policy sieht das so aus:
Header always set Reporting-Endpoints "csp-endpoint=\"https://example.com/csp-berichte\""<br>Header always set Content-Security-Policy-Report-Only "default-src 'self'; report-uri https://example.com/csp-berichte; report-to csp-endpoint"
csp-endpointist ein frei gewählter Name. Er muss nur in beiden Zeilen gleich lauten.- Die Adresse
https://example.com/csp-berichteersetzt du durch einen Endpunkt, der Berichte auch entgegennimmt. Das kann ein eigenes Skript sein oder ein externer Dienst. Bei einem externen Dienst gehen dabei Daten über deine Besucher an Dritte, das gehört in deine Datenschutzerklärung. - In deine eigene Policy fügst du die beiden Angaben einfach am Ende an, getrennt durch ein Semikolon.
- Per
<meta>-Tag funktioniert beides nicht, nur als Header.
Hast du keinen Endpunkt, lässt du beide Angaben weg und arbeitest wie im Praxis-Teil unten mit der Browser-Konsole. Das genügt für die meisten Seiten.
Mögliche Angaben
Gewertet werden nur Angaben, die ausdrücklich erwähnt werden.
Du wirst am meisten mit folgenden zu tun haben.
- ‘self’ – nur Inhalte von der eigenen Seite (gleiches Protokoll, gleiche Domain, gleicher Port)
- ‘none’ – deaktiviert einen Inhaltstyp komplett
- ‘nonce-…’ – Inline-Skripte oder -Styles mit passendem
nonce-Attribut dürfen ausgeführt werden; der Wert muss bei jedem Seitenaufruf neu erzeugt werden - ‘sha256-…’ – Inline-Skripte oder -Styles, deren Inhalt genau diesen Hash ergibt, dürfen ausgeführt werden (nur für statische Inhalte geeignet)
- ‘strict-dynamic’ – per Nonce oder Hash erlaubte Skripte dürfen weitere Skripte nachladen; Host-Angaben wie
deinwp.dewerden dann ignoriert - ‘unsafe-eval’ – erlaubt
eval()und ähnliche Funktionen, die Text als Code ausführen - ‘unsafe-hashes’ – Hashes gelten zusätzlich für Event-Handler (z. B.
onclick) undstyle-Attribute - ‘unsafe-inline’ – alle Inline-Skripte bzw. -Styles sind erlaubt; das hebelt den XSS-Schutz weitgehend aus
- ‘report-sample’ – Verstoßberichte enthalten die ersten Zeichen des blockierten Codes
Die Mozilla Foundation hat die Entwicklung maßgeblich unterstützt. Wenn du somit die Informationen aus der Quelle entnehmen möchtest, geht es hier entlang.
Schema für Quellen, Adressen und Arten
Quellen lassen sich als Adressen, in ihrer Art oder auch als Wildcards eingeben. Folgende Eingaben sind also zulässig:
- * – komplette Wildcard: alle Quellen außer
data:,blob:,filesystem:undmediastream: - deinwp.de – Skripte dürfen nur von unserer Webseite via HTTPs abgerufen werden
- *.deinwp.de – Skripte dürfen von unserer Domain und allen Subdomains abgerufen werden
- https: – jede Adresse, die mit https:// beginnt
- data: – Laden über eine Data-URL möglich (im Zusammenspiel von img-src data: typisch)
- blob: – Laden von Binärblobs möglich (BLOB = Binary Large Object, im Zusammenspiel mit object-src blob: oder media-src blob:)
- filesystem: – Laden von Filesystem: Dateien möglich
- mediastream: – Laden von Mediastream: Dateien möglich (Bsp.: für Audio- und Videoinhalte)
Praxis: CSP Header anlegen
- Als erstes testest du deine Domain auf dem Observatory von Mozilla, denn wir wollen den Gamification-Effekt nicht außer Acht lassen. Durch die Informationen aus den anderen Kapiteln solltest du nicht bei einem F oder einer niedrigen Punktzahl starten, sondern vermutlich bei etwa 50 von 100 möglichen Punkten.


- etzt setzt du
default-src 'none';, zunächst im Berichtsmodus (Content-Security-Policy-Report-Only). Die Browser-Konsole zeigt dir dann alle Verstöße mit dem Zusatz „[Report Only]“, ohne dass für deine Besucher etwas kaputtgeht.

Die Konsole hilft beim weiteren Verständnis, für alles andere hast du unsere Informationen!
Refused to load the font ‚data:font/truetype;charset=utf-8;base64,d1337GRgABA‘ because it violates the following Content Security Policy directive: „default-src ’none'“. Note that ‚font-src‘ was not explicitly set, so ‚default-src‘ is used as a fallback.
Eine Schrift kann nicht geladen werden, denn diese wird von der CSP-Regel default-src blockiert. Da es keine font-src Regel gibt, greift der Fallback
Also, müssen wir an dieser Stelle zusätzlich font-src hinzufügen. An anderer Stelle möchte die Seite eine Schriftart via data: laden, wodurch die Angabe font-src ’self‘; nicht genügt, sondern font-src ’self‘ data:; geschrieben werden muss.
Note also that ’style-src‘ was not explicitly set, so ‚default-src‘ is used as a fallback.
Die Angabe style-src gibt es noch nicht, wodurch ein Fallback greift.
Also füge ich zunächst style-src ’self‘ ein und teste mich nach und nach an die minimale Funktion der Einstellungen.
Refused to apply inline style because it violates the following Content Security Policy directive: „style-src“. Either the ‚unsafe-inline‘ keyword, a hash (’sha256-5XXBx91e/qXQZQm1337ej2N5TBLzxSWKK1N0Nvym+6rE=‘), or a nonce (’nonce-…‘) is required to enable inline execution.
Die Ressource wird durch eine Direktive unter style-src nicht ausgeführt. Es soll ‚unsafe-inline‘ hinzugefügt werden, oder den genauen sha256 Hash-Wert oder via nonce einbinden.
Mit der Angabe eines Hashes erstellst du dir eine Whitelist für ausführbare Skripte oder Styles. Das geht allerdings nur so lange gut, bis sich am jeweiligen Code etwas ändert; dann muss ein neuer Hash erzeugt werden. Behalte das also im Blick. Für Event-Handler und style-Attribute brauchst du zusätzlich 'unsafe-hashes'.
Besser ist die Lösung via ’nonce-…‘ aber diese Angabe muss vom Theme und den Plugins unterstützt werden.
Refused to apply inline style because it violates the following Content Security Policy directive: „style-src ’self‘ ’sha256-OyKg6OHgnmapAcgq1337yGA58wB21FOR7EcTwPWSs54E='“. Either the ‚unsafe-inline‘ keyword, a hash (’sha256-biLFinpqYMtWHmXfkA1BPeCY0/fNt46SAZ+BBk5YUog=‘), or a nonce (’nonce-…‘) is required to enable inline execution. Note that hashes do not apply to event handlers, style attributes and javascript: navigations unless the ‚unsafe-hashes‘ keyword is present.
Klingt ähnlich der Anweisung davor
Refused to load the image “ because it violates the following Content Security Policy directive: „default-src ’none'“. Note that ‚img-src‘ was not explicitly set, so ‚default-src‘ is used as a fallback.
Frei übersetzt: Bitte gib img-src ’self‘ an
- Anweisungen einfügen, die bis dato noch keine Fehler verursachen. Oben findest du die Auflistung aller Direktiven, mit denen du dich beschäftigen könntest. Unbedingt benennen solltest du: frame-ancestors , base-uri und form-action . Mit der Angabe ’self‘ liegst du zumeist richtig.
- Teste die Funktionen deiner Webseite. Besonders Seiten auf denen viele Plugins verwendet werden oder beispielsweise ein Kontaktformular existiert. Des weiteren solltest du nach jeder Aktualisierung schauen, ob die Angaben noch aktuell sind. Nimm daher die Prüfung in deine Routine auf.

Ein häufig anzutreffender Cookie im WordPress-Universum ist PHPSESSID. Mit diesem verknüpft der Browser unterschiedliche Tabs und erinnert sich an den Aufruf der Seite. Normalerweise findest du die PHP-Sitzung im Verzeichnis /tmp/ auf deinem Webserver. Sobald du deinen Browser schließt, wird auch die Session beendet.
Plugin- und Theme-Entwickler verwenden nur noch selten diese eher veraltete Technik7.
inde zuerst heraus, welches Plugin oder Theme die Session startet. Hast du SSH-Zugang, hilft dieser Befehl im WordPress-Verzeichnis:
grep -rn "session_start" wp-content/plugins wp-content/themes
Ohne SSH deaktivierst du die Plugins nacheinander (am besten auf einer Testkopie) und prüfst jeweils, ob das Cookie noch gesetzt wird. Brauchst du das Plugin nicht, ist das Problem damit gelöst.
Brauchst du es doch, sichere das Cookie wenigstens ab. Füge dazu in die wp-config.php oberhalb der Zeile /* That's all, stop editing! */ Folgendes ein:
@ini_set('session.cookie_secure', '1'); // nur über HTTPS senden<br>@ini_set('session.cookie_httponly', '1'); // für JavaScript unsichtbar<br>@ini_set('session.cookie_samesite', 'Lax'); // nicht bei fremden Formular-Absendungen mitschicken<br>@ini_set('session.use_strict_mode', '1'); // keine von außen vorgegebenen Session-IDs annehmen
cookie_samesite setzt PHP 7.3 oder neuer voraus. Startet ein Plugin die Session auf eine ungewöhnliche Weise, greifen die Einstellungen eventuell nicht. Prüfe deshalb in den Entwicklerwerkzeugen unter Application → Cookies, ob bei PHPSESSID die Häkchen bei „Secure“ und „HttpOnly“ gesetzt sind.
Besser wäre es natürlich, zu schauen welches Theme/Plugin die Session eröffnet und hier anzusetzen.

Hey, vielen Dank für den Tipp mit der Konsole! So konnte ich schnell rausfinden, welcher Eintrag meine Formulare blockiert.
Viele Grüße!
Hallo,
gute Erklärung. Aber script-src ’self‘ reicht meist nicht aus. Denn sobald man den Google-Tag-Manager nutzt, wird dieser in der Console bemängeln, dass unsafe-inline und unsafe-eval noch benötigt wird. Da steigen zwar meine Nackenhaare auf, aber die Kunden wollen das leider immer noch einsetzen für ihre hausinternen Statistiken.
Sobald unsafe-inline und unsafe-eval aktiviert sind, fällt man wieder durch den Test.
Ist also noch immer sehr schwer 100% Save zu werden, wenn man auf Google und Konsorten angewiesen ist.
Hey Jörg, deshalb hatte ich das – eventuell noch nicht deutlich genug – geschrieben, dass man an solchen Stellen nachjustieren kann/sollte.
Am Ende, kann die Website auch mittels CSP-Anweisungen genauso unsicher eingestellt werden, wie wenn man diese weglässt, da überall unsafe-inline und Co. verwendet wird. Meist geschieht dies aber so negativ extrem in der Praxis nicht 😉
100 % Sicherheit gibt es eh nicht und irgendwelche Kompromisse müssen wir eh eingehen, aber Gedanken sollte man sich halt mal gemacht haben. Im besten Fall entscheiden wir als Webbastler, an welcher Stelle wir dies bewusst nutzen!