CSP Nonce Header für Angular Anwendungen mit nginx Container
Durch CSP Header (Content Security Policy) lassen sich verschiedene Funktionen des Webbrowsers konfigurieren, um das Verhalten von Nachladen und auch Ausführen zusätzlicher oder dynamischer Inhalte zu beeinflussen.
Damit lassen sich typische Einfallstore für Angriffe wie Cross-Site-Scripting (XSS) wirksam abstellen.
Ist die Webanwendung jedoch eine Single Page Anwendung (SPA) und auf dynamische Erzeugung von Inhalten angewiesen, führt eine globale Deaktivierung der Dynamik dazu, dass die Anwendung nicht mehr funktioniert.
Es stellt sich daher die Frage, wie zwischen guten und potentiell schädlichen externen und dynamischen Inhalten unterschieden werden kann.
Dazu ist in allen Browsern inzwischen ein Verfahren implementiert, mit dem Entwickler solche Inhalte, die erwünscht sind, expliziert markieren können:
sogenannte "Nonce" Markierungen.
Aktuelle Frameworks, so auch Angular, bringen dazu auch speziellen Support mit.
Die Implementierung wird in diesem Beitrag anhand von nginx gezeigt.
CSP Nonce Policy Header
Ein Nonce ist ein schwer erratbarer, zufälliger Wert.
Dieser sollte dabei auch lediglich für eine kurze Zeit gültig sein.
Mit der CSP (Content Security Policy) "nonce" wird so ein zufälliger Wert verwendet, um Inhalte auf einer Webseite zu autorisieren.
Der nonce-Wert wird dabei vom Server generiert und sowohl in einem HTTP-Header als auch in den HTML-Tags der jeweiligen Skripte eingefügt.
Dies verhindert die Ausführung von Skripten, die nicht ausdrücklich erlaubt sind, und schützt vor Angriffen wie Cross-Site Scripting (XSS).
Die Prüfung erfolgt dabei automatisch durch den jeweiligen User-Agent (Browser), indem verglichen wird, ob der Header Wert und die Werte an den Tags identisch sind.
Das folgende Code-Beispiel zeigt den prinzipiellen Einsatz. Hier wird statt eines HTTP Headers ein http-equiv Meta-Tag für das Nonce verwendet.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'nonce-123';">
<title>Beispiel zur Verwendung von CSP Nonce</title>
</head>
<body>
<h1>Hallo, CSP Nonce!</h1>
<script nonce="123">
console.log('OK, this will be loggd.');
</script>
<script>
console.log('Denied, no nonce!');
</script>
<script nonce="guess">
console.log('Denied, wrong nonce!');
</script>
</body>
</html>
Generierung von CSP nonce mit nginx
Vor allem im Container-Umfeld hat der nginx Webserver eine große Beliebtheit erlangt.
Er ist schlank, schnell und lässt sich dank vorhandenem Docker-Container-Image einfach betreiben.
Natürlich ist der hier gezeigte Ansatz auch auf andere Webserver übertragbar.
Sowohl im HTTP-Header, als auch im ausgelieferten Inhalt müssen die CSP Nonces übereinstimmen.
Ein Ansatz dazu bei nginx ist die Verwendung des http_sub_module
, eine Ersetzung (Substitution) von Inhalt zum Auslieferungszeitpunkt.
Ob das aktuelle Container-Image mit dem dazu benötigten Modul ausgestattet ist, lässt sich durch die folgende Anweisung prüfen.
http_sub_module
im nginx build$ docker run --rm -it nginx nginx -V 2>&1 | tr ' ' '\n' | grep -i 'http_sub_module'
Als Nonce wird ein zufälliger Wert benötigt. Dazu eignet sich zum Besipiel die ID des aktuellen Requests oder der TLS/SSL Session. In nginx kann dazu eine separate Variable deklariert werden.
set $cspNonce $request_id; # or $ssl_session_id;
Dieser Wert wird dann im CSP Header ausgegeben.
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'nonce-$cspNonce'; style-src 'self' 'nonce-$cspNonce'; style-src-attr 'unsafe-inline'; img-src 'self' blob:;";
Im HTML wird ein Platzhalter vorgesehen, der dann durch den jeweilligen Wert ausgetauscht wird. Das könnte dann wie im folgenden Beispiel aussehen.
<script nonce="CSP_NONCE">
// inline script
</script>
Damit der Wert nun noch an der richtigen Stelle bereitgestellt wird, muss das bereits erwähnte http_sub_module aktiviert werden. Für den im Beispiel verwendete Platzhalter CSP_NONCE könnte eine Konfiguration wie folgt aussehen.
sub_filter_once off;
sub_filter_types *;
sub_filter CSP_NONCE $cspNonce;
Das zugehörige Beispiel HTML sieht dann wie folgt aus:
<!DOCTYPE html>
<html>
<head>
<title>Beispiel zur Verwendung von CSP Nonce</title>
</head>
<body>
<h1>Hallo, CSP Nonce!</h1>
<script nonce="CSP_NONCE">
console.log('OK, this will be loggd.');
</script>
<script>
console.log('Denied, no nonce!');
</script>
<script nonce="guess">
console.log('Denied, wrong nonce!');
</script>
</body>
</html>
Nun kann man nach jedem Laden der Seite sehen, dass sich der Wert des Nonce ändert und in den HTTP Headern den passenden Wert vorfinden.
CSP Nonce in Angular
Um den CSP-Nonce für Angular verfügbar zu machen, muss in der index.html
das Attribut ngCspNonce
auf die Root-Komponente gesetzt werden.
Das ist im nachfolgenden Listing gezeigt. Außerdem ist in dem Listing auch dargestellt, dass das nonce
-Attribut auf statischen (inline)-Styles und -Scripten verwendet wird.
Das ist für die korrekte Funktionsweise der Seite ebenfalls notwendig, da diese Scripte/Styles ansonsten die oben angegebene CSP verletzen würden.
Nun kann die Angular-App wie gewohnt gebaut werden. Dabei berücksichtigt Angular nun das ngCspNonce
-Attribut, und generiert Code, um auch nachträglich/lazy geladene Bestandteile der Anwendung mit dem passenden Nonce zu versehen.
index.html
mit CSP_NONCE als Platzhalter<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>My-CSP-Demo</title>
<base href="/">
<script nonce="CSP_NONCE">
const version = '';
</script>
<style nonce="CSP_NONCE">
html, body {
margin: 0;
background: #dd1d21;
}
</style>
</head>
<body>
<app-root ngCspNonce="CSP_NONCE"></app-root>
</body>
</html>
Falls das ngCspNonce
-Attribut aus irgendeinem Grund nicht gesetzt werden kann, so kann alternativ das Angular-Injection-Token CSP_NONCE
beim Start der Anwendung provided werden.
Zu den Themen Kubernetes, Docker und Angular bieten wir sowohl Beratung, Entwicklungsunterstützung als auch passende Schulungen an:
Auch für Ihren individuellen Bedarf können wir Workshops und Schulungen anbieten. Sprechen Sie uns gerne an.