HSTS, X-Frame-Options, CSP frame-ancestors und mehr richtig konfigurieren
Ein gültiges TLS-Zertifikat allein schützt einen Magento-Shop nicht vor Clickjacking, MIME-Sniffing oder stillen Datenlecks über den Referrer-Header. Dieser Artikel zeigt praxisnah, wie HSTS, X-Content-Type-Options, Referrer-Policy, Permissions-Policy und CSP frame-ancestors zusammenspielen, wie sich diese Header korrekt am Webserver oder CDN statt im Anwendungscode setzen lassen und wie Scanner wie securityheaders.com die resultierende Sicherheit bewerten.
Inhaltsverzeichnis
- 1. Warum HTTP-Security-Header unverzichtbar sind
- 2. Strict-Transport-Security: Syntax, max-age und Preload-Liste
- 3. X-Content-Type-Options: nosniff gegen MIME-Sniffing
- 4. Referrer-Policy: Datenschutz gegen Analytics
- 5. Permissions-Policy: Browser-APIs gezielt einschränken
- 6. X-Frame-Options vs. CSP frame-ancestors
- 7. Header-Scans: securityheaders.com und die Bewertung verstehen
- 8. Header am Webserver und CDN konfigurieren
- 9. Header-Reihenfolge und Duplikate: CDN gegen Origin
- 10. Zusammenfassung
- 11. FAQ
1. Warum HTTP-Security-Header unverzichtbar sind
Ein gültiges TLS-Zertifikat verschlüsselt die Verbindung, sagt dem Browser aber nichts darüber, wie er mit der Antwort umgehen soll. Genau diese Lücke schließen HTTP-Security-Header: Sie weisen den Browser explizit an, unsichere Verbindungen abzulehnen, fremde Inhalte nicht in ein iframe zu laden, Dateitypen nicht zu erraten und sensible Browser-APIs nicht ungefragt freizugeben. Ohne diese Header verlässt sich ein Magento-Shop komplett auf die permissiven Standardeinstellungen der Browser, und die sind für maximale Kompatibilität optimiert, nicht für maximale Sicherheit.
Die Konsequenzen fehlender Header sind konkret messbar: Clickjacking-Angriffe betten eine Checkout-Seite unsichtbar in ein fremdes iframe ein, MIME-Sniffing lässt eine hochgeladene Textdatei als ausführbares Skript interpretieren, und eine zu freizügige Referrer-Policy verrät Warenkorb-Parameter an Drittanbieter-Analytics. Die folgenden Abschnitte behandeln die wichtigsten Security-Header im Detail, von HSTS über nosniff und Referrer-Policy bis zur Konfiguration am Webserver statt im Anwendungscode.
2. Strict-Transport-Security: Syntax, max-age und Preload-Liste
Der Header Strict-Transport-Security (kurz HSTS) weist den Browser an, eine Domain für einen definierten Zeitraum ausschließlich über HTTPS aufzurufen, selbst wenn ein Nutzer explizit http:// eingibt oder einem unverschlüsselten Link folgt. Die Syntax besteht aus drei Teilen: max-age=<Sekunden> legt die Gültigkeitsdauer fest, includeSubDomains weitet den Schutz auf alle Subdomains aus, und preload markiert die Domain für die Aufnahme in die HSTS-Preload-Liste der Browser-Hersteller. Ein produktionstauglicher Wert liegt bei max-age=63072000, also zwei Jahren.
Ohne HSTS bleibt beim ersten Aufruf immer ein kurzes Zeitfenster für SSL-Stripping-Angriffe, weil der Browser die HTTPS-Pflicht erst nach der ersten Antwort kennt. Die Preload-Liste schließt genau diese Lücke, indem HSTS bereits vor dem ersten Kontakt fest im Browser hinterlegt ist. Die Aufnahme erfolgt über hstspreload.org und setzt voraus, dass includeSubDomains und preload auf allen Subdomains inklusive der Root-Domain fehlerfrei gesetzt sind, denn eine Entfernung aus der Liste dauert Monate und betrifft alle gängigen Browser gleichzeitig.
# nginx: HSTS and baseline security headers for a Magento storefront
server {
listen 443 ssl http2;
server_name shop.example.com;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
location / {
try_files $uri $uri/ /index.php?$args;
}
}
# Separate HTTP server block: redirect only, never send HSTS over plain HTTP
server {
listen 80;
server_name shop.example.com;
return 301 https://$host$request_uri;
}
3. X-Content-Type-Options: nosniff gegen MIME-Sniffing
Ohne den Header X-Content-Type-Options: nosniff versuchen Browser bei mehrdeutigen oder fehlenden Content-Type-Angaben, den tatsächlichen Dateityp anhand des Inhalts zu erraten, ein Verhalten namens MIME-Sniffing. Lädt ein Nutzer beispielsweise eine Datei mit der Endung .txt hoch, deren Inhalt aber wie HTML oder JavaScript aussieht, kann der Browser sie trotzdem als solches interpretieren und ausführen. In Magento-Shops mit Datei-Uploads, etwa bei Produktbewertungen mit Anhängen oder Kundenservice-Formularen, öffnet das eine reale Angriffsfläche für gespeicherte Cross-Site-Scripting-Angriffe.
Der Header selbst kennt nur einen einzigen gültigen Wert: nosniff. Gesetzt zwingt er den Browser, sich strikt an den deklarierten Content-Type zu halten und keine eigene Erkennung durchzuführen. Für Skripte und Stylesheets bedeutet das zusätzlich, dass sie nur ausgeführt beziehungsweise angewendet werden, wenn der MIME-Type exakt text/javascript beziehungsweise text/css lautet. Da der Header keine Konfigurationsoptionen hat und keine Kompatibilitätsprobleme mit modernen Browsern verursacht, gibt es praktisch keinen Grund, ihn nicht global zu setzen.
4. Referrer-Policy: Datenschutz gegen Analytics-Bedürfnisse
Die Referrer-Policy bestimmt, welche Informationen der Browser beim Verlassen einer Seite im Referer-Header an das Ziel überträgt. Der historische Standardwert no-referrer-when-downgrade sendet die vollständige URL inklusive Query-String an jede beliebige Zielseite, solange keine Herabstufung von HTTPS auf HTTP stattfindet, was in einem Magento-Shop bedeuten kann, dass Suchbegriffe, Gutscheincodes oder interne Session-Parameter an externe Werbenetzwerke durchgereicht werden.
Die empfohlene Balance zwischen Datenschutz und funktionierendem Analytics ist strict-origin-when-cross-origin: Innerhalb der eigenen Domain wird die volle URL übertragen, an fremde Domains dagegen nur der Origin ohne Pfad und Query-String, und bei einer Herabstufung von HTTPS auf HTTP wird gar kein Referrer mehr gesendet. Wer maximale Zurückhaltung braucht, etwa bei sensiblen Checkout-Flows, nutzt no-referrer und verzichtet komplett auf Referrer-Daten, verliert dann aber auch die Zuordnung eingehender Empfehlungslinks im eigenen Analytics-Tool. same-origin ist ein guter Mittelweg für Shops, die überhaupt keine Cross-Origin-Referrer benötigen.
5. Permissions-Policy: Browser-APIs gezielt einschränken
Permissions-Policy hat den älteren, inzwischen umbenannten Feature-Policy-Header abgelöst und steuert, welche mächtigen Browser-APIs eine Seite und ihre eingebetteten iframes nutzen dürfen. Ein typischer Magento-Shop benötigt weder Kamera- noch Mikrofonzugriff und in den meisten Fällen auch keine Geolocation-API, dennoch bleiben diese APIs ohne expliziten Header für jedes eingebundene Skript, einschließlich Drittanbieter-Tracking- oder Zahlungs-iframes, standardmäßig verfügbar.
Die Syntax listet pro Funktion eine sogenannte Allowlist: camera=() deaktiviert die Kamera vollständig für die Seite und alle iframes, geolocation=(self) erlaubt sie nur auf der eigenen Origin, und payment=(self "https://checkout.payment-provider.com") gibt sie gezielt für die eigene Domain und einen benannten Zahlungsanbieter frei. Diese Feinsteuerung reduziert die Angriffsfläche bei kompromittierten Drittanbieter-Skripten erheblich, weil selbst ein eingeschleustes Skript die Kamera nicht aktivieren kann, wenn die Policy es global untersagt. Für die meisten Magento-Installationen ist eine restriktive Grundeinstellung mit gezielten Ausnahmen für tatsächlich genutzte Funktionen wie Zahlungs-Widgets der richtige Ansatz.
6. X-Frame-Options vs. CSP frame-ancestors
X-Frame-Options war über Jahre der Standardschutz gegen Clickjacking und kennt zwei praxisrelevante Werte: DENY verhindert jede Einbettung in ein iframe, unabhängig vom Ursprung, während SAMEORIGIN Einbettung nur von der eigenen Domain erlaubt. Für Checkout-Seiten und den Account-Bereich eines Magento-Shops ist DENY fast immer die richtige Wahl, da es keinen legitimen Grund gibt, diese Seiten fremd einzubetten.
Der Header hat eine strukturelle Schwäche: Er erlaubt nur einen einzigen Ursprung oder gar keinen, eine Liste mehrerer erlaubter Domains ist nicht möglich. Die Content-Security-Policy-Direktive frame-ancestors löst genau das und ist der moderne Ersatz: frame-ancestors 'self' https://partner-portal.example.com erlaubt Einbettung von der eigenen Domain und einem benannten Partner gleichzeitig. Wichtig für die Übergangszeit: frame-ancestors wird von modernen Browsern bevorzugt ausgewertet, wenn beide Header vorhanden sind, während ältere Browser ausschließlich X-Frame-Options verstehen. Beide Header parallel zu setzen ist daher weiterhin sinnvolle Praxis, bis der Legacy-Header vollständig entfallen kann.
# Apache .htaccess: legacy clickjacking protection plus modern CSP replacement
<IfModule mod_headers.c>
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Content-Security-Policy "frame-ancestors 'self' https://partner-portal.example.com"
# Avoid leaking the technology stack alongside the security headers
Header unset X-Powered-By
</IfModule>
<IfModule mod_headers.c>
# DENY specifically for checkout and customer account paths
<If "%{REQUEST_URI} =~ m#^/(checkout|customer)#">
Header always set X-Frame-Options "DENY"
</If>
</IfModule>
7. Header-Scans: securityheaders.com und die Bewertung verstehen
Scanner nach dem Vorbild von securityheaders.com senden eine einzelne HTTP-Anfrage an eine URL, werten die zurückgegebenen Response-Header aus und vergeben eine Note von A+ bis F. Die Bewertung basiert auf einer festen Gewichtung: Das Fehlen von Content-Security-Policy und Strict-Transport-Security wirkt sich am stärksten auf die Note aus, während Permissions-Policy und Referrer-Policy geringer gewichtet werden. Eine Note allein sagt aber nichts darüber, ob die einzelnen Werte auch inhaltlich sinnvoll konfiguriert sind, denn ein zu offenes Access-Control-Allow-Origin: * kann trotz guter Gesamtnote ein reales Risiko bleiben.
Wichtiger als die Buchstabennote ist der Blick auf die einzelnen Fundstellen im Detailbericht: Welche Header fehlen komplett, welche sind gesetzt, aber zu freizügig konfiguriert, und welche widersprechen sich zwischen CDN und Origin-Server. Ein Scan sollte nach jedem Deployment automatisiert laufen, etwa als Schritt in der CI-Pipeline mit curl -sI gegen die Staging-URL, damit Regressionen auffallen, bevor ein Kunde den Shop im Produktivbetrieb ungeschützt erreicht.
{
"url": "https://shop.example.com/",
"grade": "B",
"score": 78,
"headers": {
"strict-transport-security": {
"present": true,
"value": "max-age=63072000; includeSubDomains; preload",
"weight": "high",
"status": "pass"
},
"content-security-policy": {
"present": false,
"weight": "high",
"status": "fail",
"note": "Missing CSP allows unrestricted script and frame sources"
},
"x-content-type-options": {
"present": true,
"value": "nosniff",
"weight": "medium",
"status": "pass"
},
"referrer-policy": {
"present": true,
"value": "strict-origin-when-cross-origin",
"weight": "low",
"status": "pass"
},
"permissions-policy": {
"present": false,
"weight": "low",
"status": "warn"
}
}
}
8. Header am Webserver und CDN konfigurieren statt im Anwendungscode
Security-Header gehören strukturell an die Infrastrukturschicht, nicht in Magento-Controller, Observer oder Layout-XML. Ein setHeader()-Aufruf in PHP läuft erst, nachdem der komplette Magento-Bootstrap durchlaufen wurde, kostet unnötig Rechenzeit und greift überhaupt nicht bei statischen Assets, Fehlerseiten oder Antworten, die direkt aus dem Full Page Cache oder einem CDN-Edge ausgeliefert werden, ohne den Applikationsserver zu erreichen. Header, die am Webserver, in Varnish oder am CDN gesetzt werden, greifen dagegen für jede Antwort, unabhängig davon, welche Schicht sie letztlich ausliefert.
Für Magento-Setups mit Varnish als Full Page Cache empfiehlt sich das Setzen der Header in vcl_deliver, sodass sie sowohl bei Cache-Hits als auch bei Cache-Misses konsistent gesendet werden. Bei einem vorgeschalteten CDN wie Fastly oder Cloudflare lassen sich dieselben Header zusätzlich über Response-Header-Regeln im CDN-Dashboard oder per Edge-Skript setzen, was besonders bei Multi-Origin-Setups mit mehreren Backends hilfreich ist. Entscheidend ist, sich auf eine einzige verantwortliche Schicht festzulegen und nicht redundant an mehreren Stellen dieselben Header zu pflegen.
<?php
// ANTI-PATTERN: setting security headers inside a Magento plugin.
// This code only runs for requests that reach PHP - cached and static
// responses served by Varnish or the CDN never pass through it.
declare(strict_types=1);
namespace Vendor\Module\Plugin;
use Magento\Framework\App\Response\Http;
/**
* Demonstrates why response headers should not be set in application code.
*/
class SecurityHeaderPlugin
{
/**
* Adds headers too late and too narrowly. Webserver/CDN configuration
* covers every response; this plugin only covers PHP-rendered ones.
*
* @param Http $subject
* @return void
*/
public function beforeSendResponse(Http $subject): void
{
// Never reached for static assets, 404 pages served by Varnish,
// or CDN edge cache hits - use nginx/Apache/Varnish/CDN instead.
$subject->setHeader('X-Content-Type-Options', 'nosniff', true);
$subject->setHeader('X-Frame-Options', 'SAMEORIGIN', true);
}
}
9. Header-Reihenfolge und Duplikate: CDN gegen Origin
Die folgende Tabelle fasst die wichtigsten Security-Header noch einmal als Checkliste zusammen: Standardzustand ohne Header gegenüber dem empfohlenen Produktivwert und der jeweiligen Schutzwirkung.
| Header | Fehlender / unsicherer Wert | Empfohlener Wert | Wirkung |
|---|---|---|---|
| Strict-Transport-Security | (kein Header gesetzt) | max-age=63072000; includeSubDomains; preload |
Erzwingt HTTPS, verhindert SSL-Stripping |
| X-Content-Type-Options | (kein Header gesetzt) | nosniff |
Verhindert MIME-Sniffing-Angriffe |
| X-Frame-Options | (kein Header gesetzt) | SAMEORIGIN / DENY |
Clickjacking-Schutz (Legacy) |
| Content-Security-Policy | (kein frame-ancestors) | frame-ancestors 'self' |
Moderner, granularer Clickjacking-Schutz |
| Referrer-Policy | no-referrer-when-downgrade |
strict-origin-when-cross-origin |
Reduziert Datenlecks an Drittseiten |
| Permissions-Policy | (kein Header gesetzt) | camera=(), microphone=(), geolocation=(self) |
Deaktiviert ungenutzte Browser-APIs |
Sobald ein CDN wie Cloudflare oder Fastly vor dem eigentlichen Webserver sitzt, setzen häufig beide Schichten dieselben Header, meist weil eine Konfiguration migriert wurde, ohne die alte zu entfernen. Das Ergebnis sind doppelte Header in der HTTP-Antwort, etwa zwei X-Frame-Options-Zeilen mit unterschiedlichen Werten. Browser reagieren darauf uneinheitlich: Manche werten nur den ersten Wert aus, manche den letzten, und bei Content-Security-Policy gilt sogar eine Sonderregel, nach der mehrere CSP-Header nicht überschrieben, sondern durch Schnittmenge kombiniert werden, was die Policy unerwartet restriktiv machen kann.
Die zuverlässige Lösung ist eine klare Zuständigkeit: Entweder das CDN setzt sicherheitsrelevante Header und der Origin-Server lässt sie unangetastet, oder umgekehrt, niemals beides gleichzeitig für denselben Header. Mit curl -sI gegen die Live-URL lassen sich doppelte Header schnell aufdecken, da jede Zeile einzeln aufgelistet wird. Bei Varnish zusätzlich beachten: vcl_deliver kann versehentlich einen bereits vom Origin gesetzten Header duplizieren, wenn set resp.http... statt eines vorherigen unset verwendet wird.
#!/usr/bin/env bash
# Detect duplicate security headers between CDN and origin
set -euo pipefail
echo "Checking for duplicate headers on shop.example.com..."
curl -sI "https://shop.example.com/" | grep -i "x-frame-options\|content-security-policy\|strict-transport-security"
# Example output revealing a duplicate (CDN and origin both set it):
# x-frame-options: SAMEORIGIN
# x-frame-options: DENY
# strict-transport-security: max-age=63072000; includeSubDomains; preload
# Fix: unset the header at one layer, keep a single source of truth.
# In Varnish vcl_deliver:
# unset resp.http.X-Frame-Options;
# set resp.http.X-Frame-Options = "SAMEORIGIN";
Mironsoft
HTTP-Security-Header, Hardening und Infrastruktur-Konfiguration für Magento-Shops
Security-Header professionell konfigurieren?
Wir analysieren die aktuelle Header-Konfiguration eures Magento-Shops auf Webserver-, Varnish- und CDN-Ebene, schließen Lücken bei HSTS, CSP und Permissions-Policy und beseitigen Duplikate zwischen den Schichten.
Header-Audit
Vollständiger Scan aller Response-Header inklusive CDN- und Origin-Vergleich
Webserver-Hardening
HSTS, nosniff, frame-ancestors und Permissions-Policy in nginx, Apache und Varnish
Monitoring-Setup
Automatisierte Header-Scans in der CI-Pipeline nach jedem Deployment
10. Zusammenfassung
Die wichtigsten Security-Header für Magento-Shops lösen jeweils ein spezifisches Risiko: Strict-Transport-Security erzwingt HTTPS und schließt mit der Preload-Liste die letzte Lücke vor dem allerersten Seitenaufruf. X-Content-Type-Options: nosniff unterbindet MIME-Sniffing-Angriffe über Datei-Uploads. Referrer-Policy und Permissions-Policy reduzieren, welche Daten und Browser-APIs überhaupt für Drittanbieter erreichbar sind. X-Frame-Options und die moderne CSP-Direktive frame-ancestors verhindern Clickjacking, wobei beide Header parallel gesetzt werden sollten, bis ältere Browser vollständig aus dem Traffic verschwunden sind.
Entscheidend für den Erfolg ist die richtige Konfigurationsschicht: Header gehören an den Webserver, nach Varnish oder ans CDN, nicht in Magento-Controller oder Plugins, da nur dort jede Antwort inklusive Cache-Hits und statischer Assets abgedeckt wird. Wer CDN und Origin gleichzeitig konfiguriert, muss auf doppelte Header achten und die Zuständigkeit klar trennen. Ein regelmäßiger Scan mit einem Tool wie securityheaders.com in der CI-Pipeline stellt sicher, dass neue Deployments keine stillen Regressionen bei der Header-Konfiguration einschleusen.
Security-Header im Überblick, das Wichtigste auf einen Blick
HSTS & Preload
max-age=63072000; includeSubDomains; preload erzwingt HTTPS ab dem ersten Aufruf. Anmeldung über hstspreload.org.
nosniff & Referrer-Policy
X-Content-Type-Options: nosniff verhindert MIME-Sniffing. strict-origin-when-cross-origin balanciert Datenschutz und Analytics.
Permissions-Policy & Frame-Schutz
Ungenutzte APIs wie Kamera und Mikrofon deaktivieren. frame-ancestors ersetzt X-Frame-Options mit mehreren erlaubten Domains.
Konfiguration & Testing
Header am Webserver, in Varnish oder am CDN setzen, nie im Magento-Code. Nach jedem Deployment mit einem Scanner prüfen.