Die sechs Sicherheits-Header, die jede Website setzen sollte

Welche HTTP-Header das Risiko wirklich senken, wogegen jeder schützt, die genauen Werte und die Fehler, die eine Content-Security-Policy nutzlos machen.

9 Min. Lesezeit

Jede Antwort Ihres Webservers beginnt mit einigen Zeilen Header. Browser lesen sie vor der Seite, und manche ändern, was der Browser der Seite erlaubt: ob sie in einen Frame auf einer anderen Website eingebettet werden darf, ob ein von einem Angreifer eingeschleustes Skript laufen kann, ob der nächste Besuch HTTPS verwenden muss. Sie kosten nichts beim Einrichten und nichts im Betrieb. Die meisten Websites setzen sie trotzdem nicht, und ein Perimetry-Audit einer typischen Website eines kleinen Unternehmens findet drei oder vier fehlende.

Dieser Leitfaden behandelt die sechs, auf die es ankommt, mit Werten zum Kopieren. Er behandelt nicht die, die nichts mehr bewirken (X-XSS-Protection, Expect-CT, Public-Key-Pins); wenn Sie diese setzen, entfernen Sie sie.

Der Rahmen in Deutschland

Im IT-Grundschutz-Kompendium des BSI gehören Sicherheits-Header zu den Bausteinen APP.3.1 „Webanwendungen und Webservices“ und APP.3.2 „Webserver“. Für die Transportverschlüsselung darunter gibt das BSI in der TR-02102-2 „Kryptographische Verfahren: Verwendung von Transport Layer Security (TLS)“ Empfehlungen zu Protokollversionen und Cipher-Suites; für Projekte der Bundesregierung gelten die Vorgaben der TR-03116-4 („Kryptographische Vorgaben für Projekte der Bundesregierung“, Teil 4) und für die Bundesverwaltung der Mindeststandard des BSI zur Verwendung von TLS. Unternehmen sind an die beiden letzteren nicht gebunden, aber sie sind ein guter Maßstab dafür, was ein Prüfer unter Stand der Technik versteht, auch im Sinne von § 30 BSIG für Einrichtungen unter NIS2.

1. Strict-Transport-Security

Strict-Transport-Security: max-age=31536000; includeSubDomains

Hat ein Browser diesen Header einmal auf Ihrer Website gesehen, verweigert er ein Jahr lang Verbindungen über unverschlüsseltes HTTP, auch wenn jemand http:// eintippt oder einen alten Link anklickt. Damit schließt sich das Zeitfenster, in dem eine Weiterleitung von HTTP auf HTTPS in einem feindlichen Netz abgefangen werden kann. Er zählt nur, wenn er über HTTPS gesendet wird, und max-age sollte mindestens sechs Monate betragen (15.552.000 Sekunden); Perimetry meldet kürzere Werte.

Fügen Sie includeSubDomains nur hinzu, wenn jede Subdomain über HTTPS ausgeliefert wird, was ohnehin der Fall sein sollte. Fügen Sie preload nur hinzu, wenn Sie die Domain anschließend in die Preload-Liste der Browser eintragen lassen; das ist eine Einbahnstraße, lesen Sie also vorher die Bedingungen.

2. Content-Security-Policy

Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self'; upgrade-insecure-requests

Die Richtlinie legt fest, woher der Browser jede Art von Ressource laden darf. Ihr Hauptzweck ist, Cross-Site-Scripting nutzlos zu machen: Ein Angreifer, dem es gelingt, <script> in eine Seite einzuschleusen, gewinnt nichts, wenn die Richtlinie Skripte nur von Ihrer eigenen Domain und nicht inline erlaubt.

Sie ist auch der Header, der am häufigsten so gesetzt wird, dass er nichts schützt. Perimetry bewertet eine Richtlinie, statt nur ihr Vorhandensein festzustellen, und stuft sie als schwach ein, wenn:

  • script-src 'unsafe-inline' ohne Nonce oder Hash enthält. Das erlaubt genau die eingeschleusten Skripte, die der Header blockieren soll.
  • script-src 'unsafe-eval' oder eine Wildcard wie * oder https: enthält.
  • object-src fehlt, sodass Plugins aus der Flash-Zeit und <object>-Einbettungen nicht beschränkt sind. Setzen Sie es auf 'none'.
  • base-uri fehlt, sodass ein eingeschleustes <base>-Tag jede relative URL der Seite umleiten kann.
  • der Header nur im Report-Only-Modus gesendet wird und nichts ihn durchsetzt.

Setzen Sie die Richtlinie zunächst eine Woche lang als Content-Security-Policy-Report-Only mit einem report-to-Endpunkt ein, beheben Sie die Verstöße (meist Inline-Skripte und Widgets von Drittanbietern) und schalten Sie dann auf Durchsetzung um. Astro, Next.js und die meisten Frameworks können Hashes oder Nonces für die Inline-Skripte erzeugen, die sie brauchen.

3. X-Frame-Options und frame-ancestors

X-Frame-Options: DENY
Content-Security-Policy: ...; frame-ancestors 'none'

Beide sagen dasselbe: Niemand darf Ihre Seite in einem Frame anzeigen. Ohne sie kann ein Angreifer Ihre Website in einem unsichtbaren Frame auf seiner Seite laden und eine angemeldete Person dazu bringen, auf Buttons zu klicken, die sie nicht sieht (Clickjacking). frame-ancestors ist die moderne Direktive und erlaubt Ausnahmen (frame-ancestors 'self' https://partner.example); X-Frame-Options bleibt für sehr alte Browser. Setzen Sie beide.

4. X-Content-Type-Options

X-Content-Type-Options: nosniff

Sagt dem Browser, dem gesendeten Content-Type zu vertrauen und nie zu raten. Ohne ihn kann eine Datei, die Sie als Text ausliefern, als Skript interpretiert werden, wenn ihr Inhalt danach aussieht, und aus einer Upload-Funktion wird ein Weg zum Einschleusen von Skripten. Es gibt keinen Grund, ihn nicht zu setzen.

5. Referrer-Policy

Referrer-Policy: strict-origin-when-cross-origin

Steuert, wie viel von der aktuellen URL an andere Websites geht, wenn jemand einem Link folgt oder eine Ressource geladen wird. Moderne Browser verwenden diesen Wert bereits als Standard, aber ihn ausdrücklich zu setzen schützt Besucher mit älteren Browsern und dokumentiert die Entscheidung. Enthalten Ihre URLs etwas Vertrauliches (ein Token zum Zurücksetzen, eine Suchanfrage, eine Kundennummer), verwenden Sie no-referrer oder same-origin. Das ist auch eine Frage des Datenschutzes: Was in der URL steht, landet sonst in den Logs fremder Server.

6. Permissions-Policy

Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=(), interest-cohort=()

Legt fest, welche Browserfunktionen Ihre Seiten (und darin eingebettete Frames) nutzen dürfen. Braucht eine Website die Kamera nicht, sorgt diese Angabe dafür, dass auch ein kompromittiertes Skript eines Drittanbieters sie nicht anfordern kann. Funktionen, die Sie nutzen, führen Sie mit (self) auf.

Wo man sie setzt

Auf dem Server oder dem Reverse Proxy, nicht in Seitenvorlagen, damit jede Antwort (auch Fehlerseiten, Weiterleitungen und statische Dateien) sie trägt:

  • Nginx: add_header im Server-Block, mit always, damit sie auch auf Fehlerseiten gesendet werden.
  • Apache: Header always set im virtuellen Host oder in .htaccess.
  • Caddy: ein header-Block.
  • Cloudflare: Rules → Transform Rules → Modify Response Header, was auch funktioniert, wenn der Ursprungsserver sie nicht sendet.
  • Node und Next.js: eine Middleware; das Paket helmet setzt vernünftige Standardwerte.
  • Astro: security.csp in der Konfiguration erzeugt eine CSP mit Hashes; die übrigen Header gehören in den Server vor dem Build.

Bei Shared Hosting mit Apache reicht meist eine .htaccess im Wurzelverzeichnis; fragen Sie Ihren Hoster, ob mod_headers aktiviert ist.

Die CSP ist die Ausnahme: Weil sie davon abhängt, was die Seite tatsächlich lädt, setzt sie oft die Anwendung statt des Proxys.

Das Ergebnis prüfen

Laden Sie Ihre Website, öffnen Sie die Entwicklerwerkzeuge des Browsers und sehen Sie sich die Antwort-Header des Hauptdokuments an. Schauen Sie dann in die Konsole: CSP-Verstöße werden dort mit der blockierten Ressource und der blockierenden Direktive protokolliert. Die Webprüfung von Perimetry liest dieselben Header auf Ihrem Haupthost und auf bis zu fünf weiteren Subdomains mit Webauftritt, bewertet die CSP und meldet Cookies ohne Secure und HttpOnly sowie CORS-Richtlinien, die jede Origin mit Anmeldedaten zulassen, die beiden benachbarten Fehler. Für einen schnellen Blick ohne Konto gibt es unsere kostenlose Zertifikats- und Sicherheits-Header-Prüfung.

So sieht der Bericht aus

Content-Security-Policy erlaubt Inline-Skripte — mittel https://www.ihre-domain.de sendet eine Richtlinie, aber script-src enthält 'unsafe-inline' ohne Nonces oder Hashes, sodass eingeschleuste Skripte laufen würden. Ersetzen Sie 'unsafe-inline' durch Nonces je Antwort oder die Hashes Ihrer Inline-Skripte und fügen Sie object-src 'none'; base-uri 'self' hinzu.

Ein fehlender Header ist ein Befund, ein vorhandener, aber schwacher ein anderer: Eine Website, die alles setzt, aber schlecht, wird nicht so bewertet, als wäre alles in Ordnung.