Du bist Entwickler:in: Deine Freude über die erfolgreich abgewehrten XSS-Angriffe war groß. Die iframe
-Sandbox und die im letzten Beitrag vorgestellten Content Security Policies (CSPs) haben hervorragende Arbeit geleistet.
Doch die Digitalisierungsstrategie deines Lieblings-Online-Shops schreitet voran: Um Ladezeiten weiter zu optimieren und die Hosting-Kosten zu senken, wird beschlossen, alle statischen Assets – darunter auch die kompilierten JavaScript-Bundles und CSS-Dateien deiner Angular-Anwendung – auf einen externen Cloud-Provider auszulagern.
Das klingt nach einer guten Idee, bis du merkst, dass dies deine bestehende CSP ins Wanken bringt. Deine bisherige CSP limitiert die Quellen von Inhalten, sodass der Browser nur Inhalte von vordefinierten Quellen oder Domains ausführt. Standardmäßig blockiert eine CSP auch alle Inline-Skripte und -Styles.
Und genau hier liegt der Haken bei Angular-Anwendungen: Hast du dich jemals gefragt, wie die gekapselten Komponenten-Styles eigentlich funktionieren? Angular injiziert diese Styles zur Laufzeit als <style>
-Tags direkt in den <head>
deines HTML-Dokuments. Das Laden der Styles wird durch deine bisherigen Konfiguration auf einmal verhindert und die Webseite schaut nicht so aus, wie sie aussehen soll. Eine „Quick Fix“-Lösung rettent den Tag: style-src: 'unsafe-inline'
in deiner CSP zu erlauben.
Als Angreifer:in: Dir ist dieser Umzug der Assets nicht verborgen geblieben. Du reibst dir die Hände. Du weißt genau, dass Angular für seine Styles auf Inline-<style>
-Tags angewiesen ist und viele Entwickler dafür unsafe-inline
in der CSP erlauben müssen. Das ermöglicht zwar Angular, seine Styles zu injizieren, aber es erlaubt gleichzeitig jedem (also auch einem Angreifer), Inline-Styles zu platzieren. Die style-src
-Regel wirkt dann nicht mehr nur für Angular, sondern für alle Quellen. Jetzt, wo die Assets von einem externen Dienst kommen und die URLs noch unberechenbarer werden, ist das die perfekte Schwachstelle. Du wetzt die Klingen, bereit, über manipulierte Style-Eigenschaften oder CSS-Expressions Nutzerdaten abzugreifen oder die Darstellung der Seite zu verzerren – denn das unsafe-inline
gibt dir die Freiheit dazu.
Die Rettung: CSP mit Style-Nonces in Angular 16!
Du bist Entwickler:in: Zum Glück hast du gehört, dass Angular 16 eine lang ersehnte Funktion mitbringt: Unterstützung für Nonces in Inline-Styles! Das ist die Lösung, auf die du gewartet hast. Du kannst unsafe-inline
endlich aus deiner CSP verbannen und trotzdem die Funktionalität deiner Angular-Styles beibehalten.
Eine Nonce (Number used once) ist ein zufälliger Wert, der bei jeder Anfrage vom Server generiert wird. Dieser Wert wird sowohl im HTTP-Header der CSP (oder im <meta>
-Tag) als auch im jeweiligen <style>
-Tag mitgeliefert. Das Prinzip ist simpel, aber genial: Nur <style>
-Tags, deren nonce
-Attribut mit dem im CSP-Header gesendeten Wert übereinstimmt, dürfen vom Browser angewendet werden.
Wie funktioniert das für deine Client-Side Angular-App?
Da Angular eine Single-Page-Application (SPA) ist, wird initial eine index.html
-Datei vom Server ausgeliefert. Diese index.html
ist der zentrale Punkt für die Nonce-Implementierung, da dein Webserver hier den Nonce-Wert dynamisch einfügen muss.
- Änderungen in der
index.html
deiner Angular-Anwendung:
Du musst zwei Dinge in deinerindex.html
anpassen. Denk daran, dass diese Anpassungen Platzhalter enthalten, die später von deinem Webserver ersetzt werden:
<!doctype html>
<html lang="en">
<head>
<title>Online Shop</title>
<base href="/">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'nonce-random_nonce_value'; script-src 'self' 'unsafe-eval';">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root ngCspNonce="random_nonce_value"></app-root>
</body>
</html>
- CSP-Definition: Die CSP selbst wird hier weiterhin im
<meta>
-Tag definiert. Beachte, dass wirstyle-src
von'unsafe-inline'
auf'self' 'nonce-random_nonce_value'
ändern. Der Stringrandom_nonce_value
ist hier ein Platzhalter. ngCspNonce
Attribut: Du fügst dem Root-Element deiner Angular-Anwendung (<app-root>
) das neue AttributngCspNonce
hinzu und gibst ihm den gleichen Platzhalterwert wie in der CSP. Angular 16 nutzt dieses Attribut, um zu wissen, welchen Nonce-Wert es in die dynamisch erzeugten<style>
-Tags einfügen soll.
- Änderungen in der Nginx-Konfiguration (Dein Webserver):
Dein Nginx-Webserver muss nun bei jeder Anfrage derindex.html
zwei Dinge tun:
a. Einen neuen, eindeutigen Nonce-Wert generieren.
b. Diesen Nonce-Wert sowohl in denContent-Security-Policy
-Header als auch an den Stellen, wo der Platzhalterrandom_nonce_value
in derindex.html
steht, einfügen.
Dafür nutzen wir dassub_filter
-Modul von Nginx. Dies ist der serverseitige Anteil, der notwendig ist, um die Nonce-Werte dynamisch zu handhaben.
server {
listen 80;
server_name your-angular-shop.com;
# Ermöglicht das Ersetzen von mehreren Vorkommen des Platzhalters sub_filter_once off;
# Ersetzt 'random_nonce_value' durch den Nginx $request_id (ein eindeutiger Wert pro Anfrage) sub_filter random_nonce_value $request_id;
# Setzt den CSP-Header und nutzt den $request_id als Nonce-Wert add_header Content-Security-Policy "default-src 'self'; style-src 'self' 'nonce-$request_id'; script-src 'self' 'unsafe-eval';";
location / {
root /path/to/your/angular/dist; # Pfad zu deinen Angular-Build-Dateien
index index.html;
try_files $uri $uri/ /index.html;
# WICHTIG: Die Filterung muss hier aktiviert werden,
# damit die Ersetzung in der index.html stattfindet. sub_filter_types text/html;
}
}
Was passiert hier?
sub_filter_once off;
: Stellt sicher, dass Nginx alle Vorkommen des Platzhalters (hierrandom_nonce_value
) in derindex.html
ersetzt und nicht nur das erste.sub_filter random_nonce_value $request_id;
: Dies ist das Herzstück. Nginx ersetzt den Platzhalterrandom_nonce_value
durch den internen$request_id
. Dieser$request_id
ist ein Wert, der von Nginx pro Anfrage generiert wird und somit eindeutig ist.add_header Content-Security-Policy "..."
: Nginx fügt diesen Header zur HTTP-Antwort hinzu. Beachte, dass auch hier der$request_id
als Nonce-Wert imstyle-src
verwendet wird.sub_filter_types text/html;
: Stellt sicher, dass dassub_filter
-Modul nur auf HTML-Dateien angewendet wird.
Warum rettet uns das den Tag?
Dein Angular 16 Frontend profitiert nun von einer deutlich strengeren CSP:
- Kein
unsafe-inline
mehr für Styles: Da Angular jetzt den dynamisch generierten Nonce-Wert in seine<style>
-Tags einfügt, kannst duunsafe-inline
aus deinerstyle-src
-Direktive entfernen. Das schließt eine große Angriffsfläche! Der Umzug der statischen Assets zum Cloud-Provider erfordert keine weitreichenden Ausnahmen in deiner CSP mehr, da die Validierung nun über die Nonce erfolgt, anstatt über feste Domain-Listen. - Schutz vor Injections: Die Angreifer können den zufälligen
$request_id
(dein Nonce-Wert) nicht vorhersagen. Selbst wenn es ihnen gelingt, bösartigen CSS-Code oder<style>
-Tags in die Webseite einzuschleusen, wird dieser nicht angewendet, es sei denn, er trägt den korrekten, aktuellen Nonce-Wert. Da der Angreifer diesen Wert nicht kennt und ihn nicht vorhersagen kann, scheitert der Angriff.
Wichtige Hinweise und Einschränkungen:
$request_id
ist nicht kryptografisch sicher: Während$request_id
eindeutig pro Anfrage ist, ist er nicht als kryptografisch sicherer Nonce-Wert konzipiert. Für sehr hohe Sicherheitsanforderungen könnte ein stärkerer, zufälliger Wert, generiert von einem Backend-Dienst, der dann über Nginx in dieindex.html
injiziert wird, besser sein. Für viele Anwendungsfälle ist$request_id
jedoch eine praktische und effektive Verbesserung.- Andere dynamische
<style>
-Tags: Diese Lösung deckt die von Angular selbst erzeugten<style>
-Tags ab. Wenn du andere Bibliotheken oder Frameworks verwendest, die ebenfalls dynamisch<style>
-Tags hinzufügen, musst du prüfen, ob diese ebenfalls einnonce
-Attribut unterstützen oder ob du andere Maßnahmen ergreifen musst. - Langlebige Sessions: Wenn Nutzer deine Angular-Anwendung über sehr lange Zeiträume geöffnet haben (ohne Neuladen der
index.html
), bleibt der einmal gesetzte Nonce-Wert aktiv. Dies ist typischerweise kein großes Problem, da XSS-Angriffe meist darauf abzielen, bei der initialen Ladezeit oder bei Navigation eine Schwachstelle auszunutzen.
Stay tuned!
Dein Online-Shop ist wieder ein Stück sicherer! Die Angreifer haben es nun noch schwerer, bösartigen Code einzuschleusen, selbst wenn Assets von externen Cloud-Providern geladen werden. Mit Angular 16 und einer cleveren Nginx-Konfiguration bist du bestens gerüstet für die Herausforderungen der modernen Web-Sicherheit. Die nächste Herausforderung kommt bestimmt!
Schreib uns eine Mail – wir freuen uns auf deine Nachricht! hello@qualityminds.de oder auf LinkedIn