Web-Security: Mit Content Security Policy gegen Cross-Site Scripting, Teil 2

vor 2 Tage 1

Cross-Site Scripting (XSS) bleibt eine der am häufigsten vorkommenden Sicherheitsbedrohungen für Webanwendungen. Trotz fortschrittlicher Schutzmechanismen gelingt es Angreifern immer wieder, neue Wege zu finden, um XSS-Schwachstellen auszunutzen. Entwicklungs- und Sicherheitsteams können Content Security Policy (CSP) einsetzen, um sich effektiv gegen XSS zu schützen.

Martina Kraus beschäftigt sich schon seit frühen Jahren mit der Webentwicklung. Das Umsetzen großer Softwarelösungen in Node.js und Angular hat sie schon immer begeistert. Als selbstständige Softwareentwicklerin arbeitet sie vornehmlich mit Angular mit Schwerpunkt auf Sicherheit in Webanwendungen.

Der erste Teil der zweiteiligen Artikelserie hat die Konzepte und Vorgehensweise einer Content Security Policy erläutert. Die Fortsetzung klärt fortgeschrittene Problemstellungen und Fragen.

Moderne Webanwendungen verwenden häufig eine Vielzahl von Skripten, die dynamisch weitere Skripte nachladen. Ein Widget, das in eine Webanwendung eingebunden ist, kann zusätzliche JavaScript-Dateien benötigen, die es eigenständig nachlädt.

Wenn beispielsweise ein Widget die JavaScript-Datei https://platform/widget.js von einem externen Content Delivery Network (CDN) benötigt, muss diese Domäne in den CSP-Regeln aufgeführt sein, um die externe JavaScript-Datei laden zu dürfen:

Content-Security-Policy: script-src 'self' https://platform/widget.js

So weit, so gut. Allerdings lädt diese externe JavaScript-Datei potenziell weitere JavaScript-Dateien von anderen CDNs nach. Diese müssen ebenfalls in der CSP stehen:

Content-Security-Policy: script-src 'self' https://platform/widget.js https://platform_one/script.js https://platform_two/index.js

Dieses Spielchen kann beliebig weitergehen und in einer riesigen CSP-Regel resultieren, die stetig gepflegt werden muss.

Das ist der Grund dafür, dass viele den Content Security Policy Header lange Zeit als nicht praktikabel empfunden haben.

Glücklicherweise gab es bald mit der neuen Direktive strict-dynamic einen Ausweg aus der Misere.

Das Keyword strict-dynamic erlaubt es einem mit einem Nonce versehenen Skript, weitere Skripte zu laden und auszuführen. Das gilt auch dann, wenn die nachgeladenen Skripte nicht explizit als vertrauenswürdig in den CSP-Regeln stehen.

Bei 'strict-dynamic' gilt das Vertrauen auch für nachgeladene Skripte.

(Bild: Martina Kraus)

Wie in der Abbildung dargestellt, erlauben die CSP-Regeln dem Browser, sowohl https://platform/widget.js als auch das dafür benötigte https://platform_one/script.js zu laden und auszuführen, sofern das geladene Skript mit einem Nonce versehen wurde. Das Ausführen des Skripts https://platform_one/evil.js ist allerdings untersagt, da es weder einen Nonce aufweist noch von einem Skript geladen wird, dem der Browser bereits vertraut.

Da strict-dynamic dazu dient, das Domänenwirrwarr in einer CSP-Regel zu bekämpfen, lässt es sich nicht für URLs verwenden, sondern erlaubt nur mit einem Nonce genehmigten Skripten, zusätzliche Ressourcen zu laden und diese auszuführen. Ein Browser ignoriert automatisch URL-basierte Ausdrücke in den CSP-Regeln, wenn er auf strict-dynamic trifft.

Um CSP-Nonces zu verwenden, ist es erforderlich, bei jeder neuen Anfrage auf die Anwendung einen neuen Nonce-Wert zu berechnen und innerhalb des CSP-HTTP-Headers auszuliefern. Das setzt allerdings ein teilweise serverseitiges Rendern der Anwendung voraus, da der errechnete Nonce ebenfalls in dem script-Tag auftauchen muss. Bei einer Express-Anwendung könnte das mithilfe des npm-Pakets EJS – Embedded JavaScript Templating beispielsweise folgendermaßen aussehen:

//index.ejs <html> <body> <script nonce="<%= nonce %>" src="valid.js"></script> </body> </html> //server.js const csp_nonces = { directives: { 'script-src': [NONCE] // NONCE refers to a // freshly calculated nonce } } app.get("/", expressCspHeader(csp_nonces), (req, res) => { // Render the EJS page with the nonce // The middleware exposes the calculated nonce on req.nonce res.render(`index`, { nonce: req.nonce }); });

Die Express-Middleware expressCspHeader fügt dem CSP-HTTP-Header einen neu berechneten Nonce-Wert hinzu und stellt den Wert zudem als Request-Objekt zur Verfügung. Die Anwendung nutzt Letzteres, um den neuen Nonce-Wert der EJS-Datei zu übergeben. Beim Rendern der HTML-Seite fügt sie den übergebenen Nonce-Wert im script-Tag für den Platzhalter ein.

Wenn das Ausliefern der Anwendung jedoch ohne Template-Engine und ohne serverseitiges Rendering erfolgt, ist der Einsatz von CSP-Nonces häufig nicht möglich, und man muss stattdessen auf CSP-Hashes zurückgreifen. Bevor man auf Hashes zurückgreift, ist es jedoch empfehlenswert, sich mit den serverseitigen Rendering-Optionen des verwendeten Frameworks vertraut zu machen. Beispielsweise hat das Frontend-Framework Angular erheblich in die Entwicklung von Funktionen für das serverseitige Rendern investiert, um diesen Prozess so einfach wie möglich zu gestalten.

Ein alternativer Ansatz ist der Weg über Function as a Service (FaaS) wie AWS Lambda oder Microsoft Azure Functions. Hierbei übernimmt die FaaS lediglich das Rendern der index.html-Datei mithilfe einer Template-Engine und fügt die notwendigen CSP-Nonces automatisch an den relevanten Stellen hinzu. Alle anderen statischen Dateien wie JavaScript, Stylesheets oder Frontend-Dateien lassen sich weiterhin problemlos von Content Delivery Networks bereitstellen.

Das Einfügen von CSP-Nonces in die Content Security Policy erfordert oft Anpassungen an der Serverarchitektur, um die Sicherheit der Webanwendung zu gewährleisten.

Leider kommt es gelegentlich vor, dass Teams sich vor Cross-Site-Scripting-Angriffen schützen möchten, aber keine Möglichkeit haben, die HTTP-Response-Header ihrer Anwendung zu verändern. Einige Content-Security-Regeln können sie in dem Fall über ein Meta-Tag direkt im HTML-Dokument einbinden, um zumindest einen grundlegenden Schutz vor XSS-Angriffen zu gewährleisten.

Content-Security-Policy-Regeln lassen sich nicht nur über HTTP-Header, sondern auch in Meta-Tags innerhalb der HTML-Dokumente einer Webseite definieren. Die alternative Methode hilft vor allem, wenn man keinen direkten Zugriff auf die HTTP-Header-Konfiguration des Servers hat oder schnelle Tests durchführen möchte.

Um CSP-Regeln über ein Meta-Tag zu definieren, fügt man ein <meta>-Element in den <head>-Bereich des HTML-Dokuments ein, setzt das Attribut http-equiv auf Content-Security-Policy und gibt die Richtlinien im content-Attribut an:

<meta http-equiv="Content-Security-Policy" content="script-src 'self'">

Der Meta-Tag-Ansatz erhöht die Flexibilität, indem er die Definition oder Anpassung von CSP-Richtlinien direkt im HTML-Dokument ohne Anpassungen am Webserver ermöglicht. Außerdem beschleunigt die Definition in den Meta-Tags den Development- und Testing-Prozess, da Entwicklerinnen und Entwickler zügig verschiedene CSP-Richtlinien testen können, ohne auf die Aktualisierung von Serverkonfigurationen oder -Deployments warten zu müssen.

Leider gehen damit auch einige Einschränkungen bezüglich der Sicherheit und Funktionalität einher: Meta-Tags sind anfälliger für Manipulationen durch XSS-Angriffe, da ein erfolgreicher Angriff es ermöglichen kann, das Meta-Tag zu ändern oder zu entfernen. Außerdem lassen sich nicht alle CSP-Direktiven über Meta-Tags setzen.

Gesamten Artikel lesen