Sicherheit per Header - Kleine Zeile, große Wirkung

Security Headers sind einfache HTTP-Header mit großer Wirkung. Richtig eingesetzt, blockieren sie ganze Klassen von Angriffen wie XSS, Clickjacking oder Information Disclosure, ohne dass Code geändert werden muss. Trotzdem werden sie oft übersehen oder falsch konfiguriert. Dabei ist ihre Einführung schnell erledigt und kann in modernen Frameworks wie ASP.NET Core einfach umgesetzt werden.
XSS-Absicherung durch Policies statt Vertrauen
Cross-Site Scripting (XSS) ist nach wie vor eine der häufigsten Schwachstellen im Web. Früher setzten Entwickler auf den Browser-Header X-XSS-Protection
. Der war zwar gut gemeint, ist aber heute kaum noch relevant. Viel effektiver ist der Einsatz einer modernen Content Security Policy (CSP). Sie definiert präzise, welche Skripte, Styles, Objekte und andere Ressourcen geladen werden dürfen, und verhindert so das Einschleusen unerwünschter Inhalte. Besonders wichtig ist dabei die Kontrolle von Inline-Skripten und -Styles, die oft ein Einfallstor für Angriffe darstellen. Durch die Verwendung von Nonces oder Hashes können selbst diese sicher eingebunden werden.
Die Konfiguration kann anstrengend werden, besonders wenn Drittanbieterdienste wie Analytics oder ähnliches im Spiel sind. Aber der Aufwand lohnt sich, denn eine gut gepflegte CSP schützt zuverlässig.
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-abc123';
object-src 'none';
frame-ancestors 'none';
form-action 'self';
In diesem Beispiel erlauben wir Skripte nur von der eigenen Domain und fügen ein Nonce für Inline-Skripte hinzu.
Eine Nonce (Number used once) ist ein einmaliger, zufälliger Wert, der bei jedem Seitenaufruf neu generiert wird. Dieser Wert wird im HTTP-Header übermittelt und muss in Inline-Skripten explizit angegeben werden. Dadurch kann der Browser sicherstellen, dass nur Skripte ausgeführt werden, die vom Backend autorisiert sind. Dies bietet eine effektive Absicherung gegen Cross-Site Scripting (XSS)-Angriffe, da Angreifer keine gültige Nonce für ihre schädlichen Skripte besitzen.
object-src 'none'
verhindert das Laden von Objekten, und frame-ancestors 'none'
verbietet das Einbetten in Frames. form-action 'self'
stellt sicher, dass Daten aus Formularen nur an die eigene Domain gesendet werden.
Mehr als nur XSS: Weitere Schutzschichten per Header
Der Header X-Frame-Options
oder alternativ frame-ancestors
verhindert Clickjacking, indem er das Einbetten der Seite in iframes unterbindet. Ohne diesen Schutz könnten Angreifer unsichtbare Frames nutzen, um Benutzeraktionen wie Klicks oder Eingaben auf manipulierte Inhalte umzuleiten. Ein Beispiel wäre eine gefälschte Login-Seite, die in einem unsichtbaren Frame über die echte Seite gelegt wird. Der Benutzer glaubt, seine Daten sicher einzugeben, während sie tatsächlich an den Angreifer gesendet werden.
X-Frame-Options: DENY
Mit X-Content-Type-Options
wird sichergestellt, dass Browser sich an den deklarierten Content-Typ halten und nicht eigenständig versuchen, den Typ zu erraten. Ohne diese Absicherung könnten Angreifer Dateien mit manipulierten Inhalten bereitstellen, die der Browser falsch interpretiert und ausführt. Ein Beispiel wäre eine als Bild deklarierte Datei, die tatsächlich ein ausführbares Skript enthält. Der Browser könnte versuchen, das Skript auszuführen, was zu einer Sicherheitslücke führt.
X-Content-Type-Options: nosniff
Strict-Transport-Security (HSTS) sorgt dafür, dass die Seite ausschließlich über HTTPS geladen wird. Dies schützt vor Downgrade-Angriffen, bei denen Angreifer versuchen, eine Verbindung auf unsicheres HTTP zurückzusetzen, um sensible Daten abzufangen. Ein konkretes Szenario wäre ein Man-in-the-Middle-Angriff in einem öffentlichen WLAN, bei dem der Angreifer den Benutzer auf eine HTTP-Version der Seite umleitet, um Login-Daten oder andere sensible Informationen abzugreifen. Die maximale Gültigkeitsdauer von 31536000 Sekunden (1 Jahr) sorgt dafür, dass der Browser auch bei zukünftigen Besuchen automatisch HTTPS verwendet. Je länger diese Zeitspanne, desto langfristiger der Schutz.
Strict-Transport-Security: max-age=31536000; includeSubDomains
Die Permissions-Policy erlaubt das gezielte Einschränken von Funktionen wie Kamera, Mikrofon oder Standortfreigabe. Ohne diese Einschränkungen könnten Angreifer ungenutzte oder unnötige Berechtigungen ausnutzen, um auf sensible Daten oder Gerätefunktionen zuzugreifen. Ein Beispiel wäre eine bösartige Webseite, die ohne Wissen des Benutzers die Kamera aktiviert, um Bilder oder Videos aufzunehmen, oder die Standortdaten abruft, um Bewegungsprofile zu erstellen.
Permissions-Policy: camera=(), microphone=(), geolocation=()
CORS: Komplex, aber notwendig
CORS (Cross-Origin Resource Sharing) ist für viele Entwickler ein rotes Tuch, weil es oft “funktionierende” Frontend-Backendanbindungen plötzlich blockiert. Aber genau das ist seine Aufgabe. CORS regelt, welche Domains mit welchen Rechten auf eine API zugreifen dürfen.
Wer einfach Access-Control-Allow-Origin: *
setzt, öffnet Tür und Tor für ungewollte Datenabflüsse. Die Konfiguration sollte daher so restriktiv wie möglich sein, idealerweise nur für bekannte und kontrollierte Domains. Diese lassen sich einfach per Konfiguration oder Umgebungsvariablen steuern.
Die einzelnen Header sind:
Access-Control-Allow-Origin
: Gibt an, welche Frontend Apps auf die Ressource zugreifen dürfen. Hier sollte nur eine Liste vertrauenswürdiger Domains stehen.Access-Control-Allow-Methods
: Definiert, welche HTTP-Methoden (GET, POST, PUT, DELETE) erlaubt sind.Access-Control-Allow-Headers
: Gibt an, welche Request Header in der Anfrage erlaubt sind.
Wer eine API für Dritte bereitstellt, sollte sich zusätzlich auch Gedanken über die Access-Control-Allow-Credentials
-Einstellung machen. Diese regelt, ob Cookies, Client Zertifikate oder HTTP Basic Auth Daten automatisch vom Browser mitgesendet werden dürfen. Damit lassen sich Cross-Site-Request-Forgery (CSRF)-Angriffe verhindern, bei denen Angreifer versuchen, im Namen des Benutzers Aktionen über eine andere Frontend-Anwendung auszuführen.
// {
// "Cors": {
// "AllowedOrigins": [
// "https://example.com",
// "https://sub.example.com"
// ]
// }
// }
var allowedOrigins = Configuration.GetSection("Cors:AllowedOrigins").Get<string[]>();
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy", builder =>
{
builder.WithOrigins(allowedOrigins)
.WithHeaders(HeaderNames.ContentType, "x-api-version")
.WithMethods(["GET", "POST", "DELETE", "PUT", "PATCH"])
.AllowCredentials(); // Nur wenn nötig
});
});
app.UseCors("CorsPolicy");
Weniger ist mehr: Überflüssige Header vermeiden
Bevor wir neue Header hinzufügen, sollten wir uns um die bestehenden kümmern. Viele Webserver senden Metainformationen mit, etwa den verwendeten Webserver oder die eingesetzte .NET-Version. Header wie Server
, X-Powered-By
oder X-AspNet-Version
verraten unnötige Details über das System. Wer weiß, was im Hintergrund läuft, weiß auch, worauf er zielen kann. Diese Header sollten konsequent entfernt oder neutralisiert werden.
In ASP.NET Core lassen sich diese Header mit wenigen Zeilen Middleware-Code zuverlässig unterdrücken:
app.Use(async (context, next) =>
{
context.Response.Headers.Remove("X-Powered-By");
context.Response.Headers.Remove("X-AspNet-Version");
await next();
});
Andere Header werden im Zweifel vom Webserver gesetzt. Hier ist es wichtig, die Konfiguration des Webservers zu überprüfen und gegebenenfalls anzupassen. Bei IIS kann das über die web.config
geschehen, bei Nginx oder Apache über die jeweiligen Konfigurationsdateien:
<configuration>
<system.webServer>
<httpProtocol>
<customHeaders>
<remove name="X-Powered-By" />
<remove name="X-AspNet-Version" />
</customHeaders>
</httpProtocol>
</system.webServer>
</configuration>
Die Referrer-Policy
ist ein weiterer unterschätzter Punkt. Ohne sie senden Browser bei ausgehenden Links unnötig viel Kontext mit, wie etwa vollständige URLs oder Pfade. Das schafft Einblick in interne Strukturen, den niemand bekommen sollte.
Referer: https://example.com/internal/page?user=12345
Mit der Referrer-Policy
können wir steuern, was weitergegeben wird. Eine gute Wahl ist strict-origin-when-cross-origin
, die nur den Ursprung (Domain) und nicht den vollständigen Pfad überträgt. So bleibt die interne Struktur verborgen.
Security beginnt damit, unnötige Informationen zu vermeiden. Wer weniger preisgibt, macht es Angreifern schwerer, Schwachstellen zu finden.
Orientierung mit OWASP und Online-Tools
Wer sich unsicher ist, welche Header wichtig sind oder wie die eigene Seite aktuell abschneidet, findet Hilfe bei OWASP. Das HTTP Headers Cheat Sheet gibt eine klare Übersicht mit Empfehlungen.
Für die praktische Prüfung helfen Dienste wie securityheaders.com oder das Mozilla HTTP Observatory. Beide liefern detaillierte Analysen und konkrete Hinweise auf fehlende oder falsch konfigurierte Header.
Die Mozilla-Tools lassen sich sogar über das npm-Paket @mdn/mdn-http-observatory
automatisiert in CI/CD-Pipelines einbinden, um Regressionen frühzeitig zu erkennen. Aber auch den OWASP ZAP Scanner, den wir im Artikel Automatisierter Web Security Check mit OWASP ZAP vorgestellt haben, kann hier helfen. Er prüft nicht nur auf Sicherheitslücken, sondern auch auf fehlende Security Header und gibt konkrete Empfehlungen zur Behebung.
Umsetzung in ASP.NET Core
In ASP.NET Core lassen sich Security Headers sehr elegant per Middleware setzen. Das Paket NetEscapades.AspNetCore.SecurityHeaders
bietet eine einfache Möglichkeit, sinnvolle Header-Vorgaben per Fluent API zu konfigurieren. Damit lassen sich CSP-Regeln, das Entfernen von Headern oder die Setzung von HSTS (HTTP Strict Transport Security) zentral steuern.
var policyCollection = new HeaderPolicyCollection()
.AddFrameOptionsDeny() // X-Frame-Options: Deny
.AddContentTypeOptionsNoSniff() // X-Content-Type-Options: nosniff
.AddStrictTransportSecurityMaxAgeIncludeSubDomains(maxAgeInSeconds: 60 * 60 * 24 * 365) // HSTS: Gültig für 1 Jahr
.AddReferrerPolicyStrictOriginWhenCrossOrigin() // Referrer-Policy: strict-origin-when-cross-origin
.RemoveServerHeader() // Server: ASP.NET Core
.AddContentSecurityPolicy(builder => // Content-Seucrity-Policy
{
builder.AddObjectSrc().None(); // object-src: none
builder.AddFormAction().Self(); // form-action: self
builder.AddFrameAncestors().None(); // frame-ancestors: none
})
.AddCrossOriginOpenerPolicy(x => x.SameOrigin()); // Cross-Origin-Opener-Policy: same-origin
app.UseSecurityHeaders(policyCollection);
Security Headers sind kein Allheilmittel, aber ein wichtiger Baustein im Gesamtpaket. Ihre Implementierung ist einfach, der Impact jedoch groß. Probleme entstehen meist nicht durch technische Komplexität, sondern durch fehlende Standards, unklare Verantwortlichkeiten oder veraltete Konfigurationen.
Die wichtigsten Learnings aus der Praxis zeigen, dass Präzision bei der Konfiguration einer Content Security Policy (CSP) entscheidend ist, auch wenn dies mit einem gewissen Aufwand verbunden sein kann. Der Nutzen einer gut gepflegten CSP überwiegt jedoch deutlich, da sie eine zuverlässige Absicherung gegen viele Angriffsvektoren bietet.
Ebenso sollte die Konfiguration von Cross-Origin Resource Sharing (CORS) stets so restriktiv wie möglich erfolgen, um ungewollte Datenabflüsse zu verhindern. Bequemlichkeit darf weder hier noch anderswo über Sicherheit stehen. Schließlich sind automatisierte Prüfungen ein unverzichtbares Werkzeug, um Sicherheitsstandards langfristig einzuhalten und Regressionen frühzeitig zu erkennen.
Wer Sicherheit von Anfang an mitdenkt, muss später nicht unter Druck reagieren.
Security Headers sind einfache und wirkungsvolle Schutzmaßnahmen gegen viele Webangriffe. Sie gehören in jede Webanwendung, sollten automatisiert, gepflegt, bewusst eingesetzt und regelmäßig validiert werden.
Nutzt ihr bereits Security Headers, um eure Anwendungen abzusichern? Schau gerne bei uns auf LinkedIn vorbei und diskutiere mit.