Webseiten-Sicherheit: HTTP-Security-Header und CSP Header

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.

Was ist Webkoll?

Das Projekt wird von Internetfonden und IIS aus Schweden finanziert[1]https://internetstiftelsen.se/. Entwickelt wurde das Tool von dataskydd.net, wodurch sich die URL erklärt. Der Quellcode ist auf Github[2]https://github.com/andersju/webbkoll 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.

  1. Wenn du nun folgende Angaben in deine .htaccess Datei integrierst, sollte der obige Test schon deutlich besser ausfallen.

Selbstverständlich ist bei allen Arbeiten an deiner Webseite ein Backup sinnvoll. Dennoch ist das Schlimmste, was nach der Einbindung passieren kann, dass deine Webseite nicht mehr lädt : P Aber, sobald du die Zeilen wieder entfernst, ist nichts geschehen! Mach dir also nicht zu viele Sorgen!

Header set Strict-Transport-Security: "max-age=31536000; includeSubDomains; preload" env=HTTPS
Header set X-Frame-Options "sameorigin"  
Header set X-Content-Type-Options nosniff  
Header set X-XSS-Protection "1; mode=block"
Header set Referrer-Policy: "no-referrer"

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.

Alle Header-Angaben lassen sich über die functions.php in dein WordPress integrieren. Nachdem du die weißt, welche Angaben du dem Browser erklären möchtest, schreibst du folgendes:

/* Security header */

header('Strict-Transport-Security:max-age=31536000');
header('X-Frame-Options: SAMEORIGIN');
header('X-Content-Type-Options: nosniff');
header('X-XSS-Protection: 1; mode=block');
header("Content-Security-Policy: default-src 'self';

HSTS – HTTP Strict Transport Security

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.

Hier siehst du die Einstellungen bei all-inkl.com

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 set Strict-Transport-Security: "max-age=31536000; includeSubDomains; preload" env=HTTPS
  1. Deine Webseite benötigt ein SSL-Zertifikat (davon gehe ich folgend aus)
  2. 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
  • : – brauchst du nicht unbedingt, gefällt mir allerdings, da es eine lesbare Trennung darstellt
  • max-age=31536000 – gibt die Dauer in Sekunden an, die der Browser die HSTS-Regel zwischenspeichern soll. Maximal kannst du 31536000 Sekunden wählen, was einem Jahr entspricht.
  • env=HTTPS – stellt sicher, dass der Header nur gesendet wird, wenn deine Seite via HTTPS ausgeliefert wird
  • includeSubDomains – integriert Subdomains
  • preload – Deine Webseite wird bereits beim ersten Aufruf via HTTPS ausgeliefert, dann ist als max-age auch 2 Jahre möglich[3]https://hstspreload.org/

Referrer Policy

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 set Referrer-Policy: "no-referrer"

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

Stellt eure Regel so restriktiv wie möglich ein, verwendet also „no-referrer“ oder „same-origin“.

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 set Permissions-Policy: geolocation=(), midi=(), camera=(), usb=(), payment=(), vr=(), speaker=(), ambient-light-sensor=(), gyroscope=(), microphone=(), usb=(), interest-cohort=()

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.

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 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. 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.

So sieht unser Ziel aus

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 den Code aus und hält dabei die Same-Origin-Policy ein (Siehe X-Frame-Origin Angabe), darf also nicht auf andere Quellen zugreifen. Diese Regel kann vergleichsweise einfach ausgehebelt werden und der Browser bemerkt gar nicht, dass er fremden Code ausführt. Mit der Content-Security-Policy wird eine strikte Trennung von HTML-Code und externen JavaScript-Dateien eingeführt.

Ein relativ strikter Header für deine WordPress-Webseite könnte wie folgt aussehen. 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 angemeldeten Zustand. Hierzu fügst du innerhalb von /wp-admin/ eine weitere .htaccess ein:

<IfModule mod_headers.c> Header unset Content-Security-Policy </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

Header set Content-Security-Policy "frame-ancestors 'self'; base-uri 'self'; default-src 'none'; form-action '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';

Theorie: So legst du die CSP an

  1. Teste deine angegebenen Regeln mit dem CSP-Evaluator
  2. Nach dem Einbinden, prüfst du die Browser-Console auf Fehler
  3. 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;"

Das W3C schlägt die von mit verwendete Nennung von Content-Security-Policy vor [4]ttps://www.w3.org/TR/CSP3/, allerdings verwenden diese noch nicht alle Browser. Besonders veraltete Exemplare, wie der Internet Explorer versteht nicht alle Regeln. Um diesen Schutz übergreifend zu gewährleisten musst du den Header schlicht zusätzlich als X-Webkit-CSP und X-Content-Security-Policy angeben. Also:

  • Header set Content-Security-Policy
  • Header set X-Webkit-CSP
  • Header set X-Content-Security-Policy

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 welcher Quelle dürfen Daten in Frames auftauchen? (Dürfen Videos von Drittanbietern angezeigt werden?)
  • connect-src: Mit welchen Quellen darf sich deine Seite verbinden? (Welche Links sind zulässig?)
  • 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: Welche Plug-Ins (beispielsweise Flash) dürfen zugreifen?
  • plugin-types: Und welche Arten von Erweiterungen lässt du zu?
  • report-uri: An welche URL soll ein Bericht gesendet werden, wenn gegen deine Sicherheitsmaßnahmen verstoßen wurde?
  • script-src: Welche Quellen sind für JavaScript erlaubt?
  • style-src: Welche Quellen sind für Stylesheets erlaubt?
  • upgrade-insecure-requests: Damit legst du fest, dass HTTP-Seiten wie HTTPS-Seiten behandelt werden
  • sandbox: Die entsprechende Seite wird in eine Sandboxverschoben. Hier sind Dinge wie Formulare, Pop-ups und das Ausführen von Skripten verboten

Mögliche Angaben

Gewertet werden nur Angaben, die ausdrücklich erwähnt werden.

Du wirst am meisten mit folgenden zu tun haben

  • ’nonce-…‘ – Scripte (Inline/CSS) die das Attribut nonce verwenden, dürfen ausgeführt werden
  • ‚host-source‘
  • ’schema-source‘
  • ’self‘ – erteilt dem Browser den Befehl, dass er nur Inhalte der eigenen Quelle (Webseite) nachladen darf
  • ’sha256′ – Nur für statische Skripte oder CSS mit dem angegebenen Hash werden ausgeführt. (Bei dynamischen und gecachten Inhalten nicht geeignet)
  • ‚*nonce-…‘ – ein gesamter Script-Block wird in die Liste erlaubter Scripte aufgenommen
  • ’strict-dynamic‘
  • ‚unsafe-eval‘ – nur dynamische Code, wie JavaScript darf ausgeführt werden
  • ‚unsafe-hashes‘ – Scripts in Event-Handlern sind erlaubt (Bsp.: onclick) aber nicht auf JavaScript oder Inline-Script-Ebene
  • ‚unsafe-inline‘ – Inline-Skripte und CSS können ausgeführt werden
  • ’none‘ – deaktiviert einen Inhaltstyp komplett

Diese gibt beispielsweise noch:

  • hash-algorithm‚base64-value
  • ‚strict-dymic‘
  • report-sample

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:, medisteam:, sind erlaubt
  • 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: – solange die Domain mit HTTPS beginnt, dürfen Skripte abgerufen werden (theoretisch wäre auch ein „http:“ möglich)
  • 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

  1. 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.
  1. Jetzt blockierst du mittelst default-src ’none‘; alle Ressourcen und schaust in die Browser-Console die aufgetretenen Fehler an. Diese wird sicherlich sehr rot aussehen, also viele Fehler melden, die wir kontinuierlich abarbeiten können. Quellen gibst du möglichst exakt an und auf Angaben von Wildcards und kompletten Domains verzichtest du möglichst.

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 eine Hashes erstellst du dir eine Whitelist für ausführbare Skripte. Das geht allerdings nur so lange gut, bis am jeweiligen Script-Bereich etwas verändert wird. Dann kann ein neuer Hash generiert werden. Die Lösung ist somit etwas riskant und solle kontinuierlich beobachtet werden. Ich habe in einigen Fällen die Hash-Werte generell geöffnet: ‚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
  1. 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.
  2. 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.
So freut sich das Mozilla Observatory mit uns, wenn wir alles perfekt eingerichtet haben

First Party Cookies

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 Technik.[5]https://kinsta.com/de/blog/wordpress-cookies-php-sessions/ Wenn dir dennoch diese Cookie-Art begegnet, dann versuche die Deaktivierung wie folgt:

Besser wäre es natürlich, zu schauen welches Theme/Plugin die Session eröffnet und hier anzusetzen.

In der Console kannst du innerhalb von Chrome auf Cookies prüfen.

Quellen und Fußnoten

Quellen und Fußnoten
1 https://internetstiftelsen.se/
2 https://github.com/andersju/webbkoll
3 https://hstspreload.org/
4 ttps://www.w3.org/TR/CSP3/
5 https://kinsta.com/de/blog/wordpress-cookies-php-sessions/

1 Gedanke zu „Webseiten-Sicherheit: HTTP-Security-Header und CSP Header“

Schreibe einen Kommentar