Angular Security Teil 4 –
Wenn die Wolke ruft und
Styles rebellieren – Nonce
rettet deine Angular-App!

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.

  1. Änderungen in der index.html deiner Angular-Anwendung:
    Du musst zwei Dinge in deiner index.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 wir style-src von 'unsafe-inline' auf 'self' 'nonce-random_nonce_value' ändern. Der String random_nonce_value ist hier ein Platzhalter.
  • ngCspNonce Attribut: Du fügst dem Root-Element deiner Angular-Anwendung (<app-root>) das neue Attribut ngCspNonce 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.
  1. Änderungen in der Nginx-Konfiguration (Dein Webserver):

    Dein Nginx-Webserver muss nun bei jeder Anfrage der index.html zwei Dinge tun:

    a. Einen neuen, eindeutigen Nonce-Wert generieren.
    b. Diesen Nonce-Wert sowohl in den Content-Security-Policy-Header als auch an den Stellen, wo der Platzhalter random_nonce_value in der index.html steht, einfügen.

    Dafür nutzen wir das sub_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?

  1. sub_filter_once off;: Stellt sicher, dass Nginx alle Vorkommen des Platzhalters (hier random_nonce_value) in der index.html ersetzt und nicht nur das erste.
  2. sub_filter random_nonce_value $request_id;: Dies ist das Herzstück. Nginx ersetzt den Platzhalter random_nonce_value durch den internen $request_id. Dieser $request_id ist ein Wert, der von Nginx pro Anfrage generiert wird und somit eindeutig ist.
  3. add_header Content-Security-Policy "...": Nginx fügt diesen Header zur HTTP-Antwort hinzu. Beachte, dass auch hier der $request_id als Nonce-Wert im style-src verwendet wird.
  4. sub_filter_types text/html;: Stellt sicher, dass das sub_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 du unsafe-inline aus deiner style-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 die index.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 ein nonce-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!

send icon

Schreib uns eine Mail – wir freuen uns auf deine Nachricht! hello@qualityminds.de oder auf LinkedIn