← Zurück zum Blog

Was HTTP-Sicherheitsheader wirklich bringen

Die billigste Sicherheit, die Sie je hinzufügen werden

Der Großteil der Sicherheitsarbeit betrifft die Anwendungslogik: Eingaben validieren, Tokens sauber begrenzen, genau durchdenken, was jede Anfrage tun darf. HTTP-Sicherheitsheader sind anders. Sie setzen eine Handvoll Response-Header, der Browser erzwingt sie bei jedem Seitenaufruf, und ganze Angriffsklassen sind ausgeschlossen, ohne dass Sie eine Zeile am Verhalten Ihrer App ändern.

Das ist der Reiz: viel Wirkung pro Konfigurationszeile. Die Arbeit sitzt am Rand — eine CDN-Regel, ein Server-Block, ein paar Zeilen in Ihrem Framework — und nicht quer durch die gesamte Codebasis.

Eine Einschränkung vor der Liste, weil genau das oft falsch verstanden wird: Header sind Defense in Depth, kein Ersatz für den eigentlichen Fix. Eine Content-Security-Policy verkleinert den Explosionsradius eines Cross-Site-Scripting-Bugs; sie behebt den Bug nicht. Betrachten Sie sie als äußere Schutzschichten um Code, der selbst bereits korrekt sein soll.

Tiefengestaffelte Verteidigung mit Web-Sicherheitsheadern: ein Browser, geschützt durch eine Barriere aus übereinandergelegten HTTP-Antwort-Headern — Content-Security-Policy, Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, Referrer-Policy und Permissions-Policy — von denen jeder eine Angriffsklasse blockiert, etwa XSS, Clickjacking, MIME-Sniffing und Man-in-the-Middle

Ich gehe sie ungefähr in der Reihenfolge durch, in der ich sie hinzufügen würde: zuerst die Header mit dem besten Verhältnis von Nutzen zu Aufwand. Der letzte ist der Ausreißer — den würde ich heute entfernen.

Strict-Transport-Security — HTTPS erzwingen und erzwungen halten

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

Was er tut. Als HSTS weist dieser Header den Browser an, Ihre Website für die Dauer von max-age (hier ein Jahr in Sekunden) ausschließlich über HTTPS anzusprechen. Sobald ein Browser den Header gesehen hat, stuft er jede http://-Anfrage selbst auf https:// hoch, bevor ein einziges Byte über die Leitung geht. includeSubDomains erweitert dieses Versprechen auf jede Subdomain.

Was er verhindert. Den Downgrade-Angriff. Ohne HSTS geht die allererste Anfrage eines Nutzers — eine bloße Domain in der Adressleiste oder ein alter http://-Link — im Klartext hinaus. Genau an dieser Stelle kann ein Man-in-the-Middle in einem feindseligen Wi-Fi TLS entfernen und sich zwischen Nutzer und Server schieben. HSTS schließt dieses Fenster: Nach dem ersten Besuch versucht der Browser HTTP gar nicht mehr.

Worüber nachzudenken sich lohnt. Es gibt eine preload-Direktive und eine zugehörige Browser-Preload-Liste, die sogar die Lücke beim ersten Besuch schließt — Ihre Domain wird fest in Browsern als HTTPS-only hinterlegt. Das ist tatsächlich stärker, aber behandeln Sie es als Verpflichtung, nicht als einfachen Schalter. Auf die Liste zu kommen ist leicht; wieder herunterzukommen dauert, und es betrifft jeden Host unter Ihrer Domain. Fügen Sie preload nur hinzu, wenn Sie sicher sind, dass jede Subdomain, die Sie jemals betreiben werden, dauerhaft HTTPS unterstützt. Bis dahin ist ein max-age von einem Jahr mit includeSubDomains die sichere Voreinstellung.

Content-Security-Policy — der große Brocken, und die meiste Arbeit

Content-Security-Policy: default-src 'self'

Was sie tut. Die CSP teilt dem Browser mit, aus welchen Quellen er Ressourcen laden darf: Skripte, Styles, Bilder, Frames, Schriften, alles. default-src 'self' ist der grobe Ausgangspunkt: nur Ressourcen von der eigenen Origin laden, nichts Externes.

Was sie verhindert. Das ist Ihre stärkste Browser-seitige Antwort auf XSS. Wenn ein Angreifer <script src="https://evil.example/x.js"> in Ihre Seite einschleusen kann, sorgt eine CSP ohne diese Origin dafür, dass der Browser das Skript nicht lädt und nicht ausführt. Sie blockiert außerdem Exfiltration zu nicht freigegebenen Domains.

Die ehrliche Einschränkung. default-src 'self' ist der Anfang, fast nie das Ende. Sobald Sie Analytics, eine Web-Schrift, ein eingebettetes Video oder irgendetwas von einem CDN laden, brauchen Sie explizite Allowlists — script-src, style-src, img-src, frame-src und so weiter. Inline-<script>-Elemente und style-Attribute funktionieren nicht mehr, sofern Sie sie nicht erlauben. Und „einfach 'unsafe-inline' hinzufügen“ nimmt still und leise einen großen Teil des Schutzes wieder zurück. Eine starke moderne CSP verwendet stattdessen Nonces oder Hashes pro Anfrage plus strict-dynamic. Von allen Headern hier ist dies derjenige, der am ehesten Ihre eigene Website kaputt macht, bevor er einen Angreifer stoppt. Rollen Sie ihn deshalb zuerst mit Content-Security-Policy-Report-Only aus, beobachten Sie, was er blockiert hätte, und erzwingen Sie ihn erst danach.

Noch etwas: Die CSP-Direktive frame-ancestors ist der moderne, flexiblere Ersatz für den nächsten Header dieser Liste. Wenn Sie eine CSP schreiben, setzen Sie frame-ancestors dort ebenfalls.

X-Content-Type-Options — eine Zeile, kein Nachteil

X-Content-Type-Options: nosniff

Was er tut. Er weist den Browser an, dem deklarierten Content-Type zu vertrauen und nicht zu versuchen, den tatsächlichen Typ einer Ressource aus ihren Bytes zu erraten („sniffing“).

Was er verhindert. MIME-Sniffing. Wenn Nutzer Dateien hochladen können und Sie diese wieder bereitstellen, kann ein Angreifer etwas bauen, das als image/jpeg deklariert ist, tatsächlich aber JavaScript enthält. Ein Browser, der sniffing betreibt und entscheidet „das sieht aus wie ein Skript“, führt es aus. nosniff zwingt den Browser, den deklarierten Typ zu respektieren. Eine als Bild bereitgestellte Datei wird als Bild behandelt, Punkt.

Warum er als Erstes gesetzt wird. Es gibt im Grunde keinen Nachteil und nichts zu justieren. Ein einziger statischer Wert, der ein echtes Loch schließt. Setzen Sie ihn überall und vergessen Sie ihn.

X-Frame-Options — lassen Sie andere Websites nicht Ihre Oberfläche tragen

X-Frame-Options: DENY

Was er tut. Er verhindert, dass Ihre Seiten in einem <iframe> auf einer anderen Website eingebettet werden. DENY blockiert jedes Framing; SAMEORIGIN erlaubt nur Ihren eigenen Seiten, einander zu framen.

Was er verhindert. Clickjacking. Der Angriff lädt Ihre echte Seite in einem transparenten iframe über einem Köder. Der Nutzer glaubt, auf den harmlosen Button des Angreifers zu klicken, klickt aber in Wirklichkeit auf Ihr darunterliegendes Steuerelement „Konto löschen“ oder „Zahlung autorisieren“. Wenn Ihre Seite nicht geframt werden kann, lässt sich diese Überlagerung nicht bauen.

Die moderne Anmerkung. Wie oben ist frame-ancestors der CSP der neuere Mechanismus, und er gewinnt dort, wo beide vorhanden sind. X-Frame-Options lohnt sich noch für ältere Clients, aber wenn Sie ohnehin eine CSP schreiben, ist frame-ancestors 'none' (oder eine bestimmte Allowlist) die eigentliche Kontrolle.

Referrer-Policy — hören Sie auf, Ihre URLs preiszugeben

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

Was sie tut. Sie steuert, wie viel der Ursprungs-URL im Referer-Header mitgesendet wird, wenn ein Nutzer zu einer anderen Website weiterklickt. Dieser Wert sendet den vollständigen Pfad bei Navigation innerhalb derselben Origin, aber nur die reine Origin (https://example.com, ohne Pfad oder Query) beim Wechsel auf eine andere Website — und gar nichts beim Downgrade von HTTPS auf HTTP.

Was sie verhindert. Stille Datenschutz- und Datenlecks. Eine URL wie https://example.com/reset?token=secret sollte dieses Token niemals an irgendeinen externen Link weitergeben, den der Nutzer als Nächstes anklickt. Diese Richtlinie stellt sicher, dass nur Ihre eigene Origin nach außen gelangt.

Die Nuance. In modernen Browsern ist das bereits die Voreinstellung. Explizit gesetzt führt es also weniger ein neues Verhalten ein, sondern schreibt es fest: Es garantiert dieselbe Haltung auf älteren Clients und macht die Absicht für jeden sichtbar, der Ihre Konfiguration liest. Billig zu setzen, und es dokumentiert eine Entscheidung, die Sie bewusst getroffen haben, statt einer, die Sie geerbt haben.

Permissions-Policy — schalten Sie die Hardware ab, die Sie nicht nutzen

Permissions-Policy: camera=(), microphone=(), geolocation=()

Was sie tut. Sie deklariert, welche mächtigen Browser-Funktionen Ihre Website — und alles, was darin eingebettet ist — nutzen darf. Eine leere Allowlist wie camera=() verweigert diese Funktion vollständig.

Was sie verhindert. Den Missbrauch von Funktionen, besonders über eingebettete Drittinhalte. Wenn Ihre Website Kamera, Mikrofon oder Standort nie nutzt, verhindert eine entsprechende Policy, dass eine bösartige Anzeige oder ein kompromittiertes Skript diese APIs still anfordert oder erreicht. Sie reduzieren die Angriffsfläche auf das, was Sie wirklich brauchen.

Wissenswert. Dieser Header hat den älteren Feature-Policy ersetzt, der eine andere Syntax hatte. Wenn Sie online Beispiele für Feature-Policy finden, sind sie veraltet. Verweigern Sie die Funktionen, die Sie nicht nutzen; erlauben Sie die wenigen, die Sie nutzen, beschränkt auf Ihre eigene Origin.

X-XSS-Protection — der eine, den man ausmustern sollte

X-XSS-Protection: 0

Das ist der Header, der am schlechtesten gealtert ist. Der Rat, den man dazu noch findet — „setzen Sie 1; mode=block als Fallback“ —, ist genau der Teil, dem ich am entschiedensten widerspreche.

Er sollte den im Browser eingebauten XSS-Auditor aktivieren: einen heuristischen Filter, der reflektierte Skripte erkennen und neutralisieren sollte. Das Problem ist, dass es diesen Filter nicht mehr gibt. Chrome hat seinen XSS Auditor bereits 2019 entfernt, Edge folgte beim Wechsel zu Chromium, und Firefox hat nie einen ausgeliefert. In einem aktuellen Browser tut 1; mode=block nichts.

Schlimmer noch: Solange er aktiv war, wurde der Auditor selbst zur Angriffsfläche. Seine Erkennung ließ sich als Seitenkanal missbrauchen, um eine Seite auszukundschaften, und sein Blocking konnte legitime Inhalte unterdrücken. Deshalb lautet die aktuelle Empfehlung, auch im OWASP Secure Headers Project: Header ganz weglassen oder X-XSS-Protection: 0 senden, um verbliebenes Altverhalten ausdrücklich abzuschalten. Der echte Schutz vor XSS ist die Content-Security-Policy oben, plus diszipliniertes Output-Encoding in Ihrer App. Dieser Header ist ein Relikt; behandeln Sie ihn so.

Alles zusammensetzen

Alle diese Header leben auf der Response-Ebene und sind damit framework- und sprachunabhängig. Ein add_header in Nginx, eine Vercel- oder Cloudflare-Regel oder die Header-Konfiguration Ihres Frameworks bringen Sie ans gleiche Ziel. Hier ist der vollständige Satz in einer Next.js-Konfiguration, weil diese Website damit läuft:

// next.config.ts
const securityHeaders = [
  { key: "Strict-Transport-Security", value: "max-age=31536000; includeSubDomains" },
  { key: "Content-Security-Policy", value: "default-src 'self'" },
  { key: "X-Content-Type-Options", value: "nosniff" },
  { key: "X-Frame-Options", value: "DENY" },
  { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
  { key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=()" },
  { key: "X-XSS-Protection", value: "0" },
];

export default {
  async headers() {
    return [{ source: "/:path*", headers: securityHeaders }];
  },
};

Stimmen Sie die CSP auf Ihre echten Abhängigkeiten ab, bevor Sie sie ausrollen — diese eine Zeile blockiert Ihre eigenen Analytics-Skripte und Schriften exakt so, wie sie dasteht.

Prüfen Sie danach Ihre Arbeit, denn ein Header, von dem Sie glauben, ihn gesetzt zu haben, und ein Header, den der Browser tatsächlich empfängt, sind zwei verschiedene Dinge. curl -I https://yoursite.example zeigt genau, was über die Leitung geht, und ein Scanner wie securityheaders.com bewertet das Set und markiert, was fehlt. Ich sehe lieber die echte Response, als der Konfiguration blind zu vertrauen.

Nichts davon ersetzt eine sauber gebaute Anwendung. Aber gemessen am Aufwand — ein gutes Dutzend Zeilen, einmal gesetzt, vom Browser jedes Besuchers erzwungen — ist es einer der besten Sicherheitsgewinne pro Tastendruck. Setzen Sie die sechs Header, die ihren Platz verdienen, mustern Sie den einen aus, der ihn nicht verdient, und überprüfen Sie, was tatsächlich beim Browser ankommt.


Wenn Sie solche Themen interessieren, schreibe ich den Rest im Newsletter auf.