Cross-Site-Scripting (XSS) mit einer strengen Content Security Policy (CSP) abschwächen

Hier erfahren Sie, wie Sie eine CSP auf Basis von Script-Nonces oder -Hashes zum Schutz vor websiteübergreifenden Skripts bereitstellen.

Lukas Weichselbaum
Lukas Weichselbaum

Warum sollte ich eine strenge Content Security Policy (CSP) implementieren?

Cross-Site-Scripting (XSS), die Möglichkeit, schädliche Skripts in eine Webanwendung einzuschleusen, ist seit über einem Jahrzehnt eine der größten Sicherheitslücken im Web.

Die Content Security Policy (CSP) ist eine zusätzliche Sicherheitsebene zur Vermeidung von XSS. Zum Konfigurieren einer CSP müssen Sie einer Webseite den Content-Security-Policy-HTTP-Header hinzufügen und Werte festlegen, um zu steuern, welche Ressourcen der User-Agent für diese Seite laden darf. In diesem Artikel wird erläutert, wie Sie eine CSP, die auf Nonces oder Hashes basiert, zur Minderung von XSS anstelle der häufig verwendeten CSPs verwenden, die auf der Host-Zulassungsliste basieren. Bei diesen CSPs ist die Seite häufig für XSS verfügbar, da sie in den meisten Konfigurationen umgangen werden können.

Eine auf Nonces oder Hashes basierende Content Security Policy wird oft als strikte CSP bezeichnet. Wenn eine Anwendung eine strikte CSP verwendet, können Angreifer, die HTML-Einschleusungen feststellen, diese im Allgemeinen nicht nutzen, um den Browser zur Ausführung schädlicher Skripts im Kontext des anfälligen Dokuments zu zwingen. Das liegt daran, dass eine strikte CSP nur gehashte Skripts oder Skripts mit dem richtigen auf dem Server generierten Nonce-Wert zulässt. Daher können Angreifer das Skript nicht ausführen, ohne die richtige Nonce für eine bestimmte Antwort zu kennen.

Browserkompatibilität

Die strikte CSP wird in allen modernen Browser-Engines unterstützt.

Unterstützte Browser

  • 52
  • 79
  • 52
  • 15,4

Quelle

Wenn deine Website bereits über eine CSP wie script-src www.googleapis.com verfügt, ist sie möglicherweise nicht gegen Cross-Site-Scripting wirksam. Diese Art von CSP wird als Zulassungslisten-CSP bezeichnet und hat einige Nachteile:

Dadurch können CSPs mit Zulassungslisten in der Regel nicht wirksam daran gehindert werden, XSS auszunutzen. Daher wird empfohlen, eine strikte CSP zu verwenden, die auf kryptografischen Nonces oder Hashes basiert, um die oben beschriebenen Fallstricke zu vermeiden.

CSP mit Zulassungsliste
  • Ihre Website wird nicht effektiv geschützt. ❌
  • Muss stark angepasst werden. 😓
Strikte CSP
  • Wirksamer Schutz Ihrer Website ✅
  • Die Struktur ist immer gleich. 😌

Was ist eine strikte Content Security Policy?

Eine strikte Content Security Policy hat die folgende Struktur und wird durch Festlegen eines der folgenden HTTP-Antwortheader aktiviert:

  • Nicht standortbasierte, strikte CSP
Content-Security-Policy:
  script-src 'nonce-{RANDOM}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';

  • Hash-basierte strikte CSP
Content-Security-Policy:
  script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';

Die folgenden Eigenschaften machen eine CSP wie die obige „streng“ und damit sicher:

  • Mit Nonces 'nonce-{RANDOM}' oder Hashes 'sha256-{HASHED_INLINE_SCRIPT}' wird angegeben, welche <script>-Tags vom Entwickler der Website als vertrauenswürdig eingestuft werden und im Browser des Nutzers ausgeführt werden dürfen.
  • Legt fest, dass 'strict-dynamic' den Aufwand für die Bereitstellung einer Nonce- oder Hash-basierten CSP verringert, indem die Ausführung von Scripts, die von einem bereits vertrauenswürdigen Script erstellt wurden, automatisch zugelassen wird. Dadurch wird auch die Nutzung der meisten JavaScript-Bibliotheken und -Widgets von Drittanbietern aufgehoben.
  • Basiert nicht auf URL-Zulassungslisten und leidet daher nicht an häufigen Umgehungen von CSPs.
  • Blockiert nicht vertrauenswürdige Inline-Scripts wie Inline-Event-Handler oder javascript:-URIs.
  • object-src wird eingeschränkt, um gefährliche Plug-ins wie Flash zu deaktivieren.
  • base-uri wird eingeschränkt, um das Einschleusen von <base>-Tags zu blockieren. Dadurch wird verhindert, dass Angreifer die Speicherorte von Skripts ändern, die von relativen URLs geladen werden.

Eine strikte CSP anwenden

Für eine strikte CSP gelten folgende Voraussetzungen:

  1. Entscheiden Sie, ob Ihre Anwendung eine Nonce- oder Hash-basierte CSP festlegen soll.
  2. Kopieren Sie die CSP aus dem Abschnitt Was ist eine strikte Content Security Policy? und legen Sie sie als Antwortheader in Ihrer Anwendung fest.
  3. Refaktorieren Sie HTML-Vorlagen und clientseitigen Code, um Muster zu entfernen, die mit der CSP nicht kompatibel sind.
  4. Stellen Sie Ihre CSP bereit.

Sie können während dieses gesamten Prozesses eine Prüfung der Best Practices von Lighthouse (Version 7.3.0 und höher mit dem Flag --preset=experimental) verwenden, um zu prüfen, ob Ihre Website eine CSP hat und ob sie streng genug für XSS ist.

Warnung im Lighthouse-Bericht, dass im Erzwingungsmodus keine CSP gefunden wurde.

Schritt 1: Entscheiden, ob Sie eine Nonce- oder Hash-basierte CSP benötigen

Es gibt zwei Arten von strikten CSPs: Nonce- und Hash-basiert. Die Anwendung erfolgt so:

  • Nicht basierte CSP: Sie generieren zur Laufzeit eine Zufallszahl, nehmen sie in Ihre CSP auf und verknüpfen sie mit jedem Skript-Tag auf Ihrer Seite. Ein Angreifer kann kein schädliches Skript in Ihre Seite einschleusen und ausführen, da er die richtige Zufallszahl für dieses Skript erraten müsste. Dies funktioniert nur, wenn die Zahl nicht erraten werden kann und zur Laufzeit für jede Antwort neu generiert wird.
  • Hash-basierte CSP: Der Hash jedes Inline-Script-Tags wird der CSP hinzugefügt. Jedes Skript hat einen anderen Hash. Angreifer können kein schädliches Skript auf deiner Seite ein- und ausführen, da der Hash dieses Skripts in deiner CSP vorhanden sein müsste.

Kriterien für einen strikten CSP-Ansatz:

Kriterien für die Auswahl eines strikten CSP-Ansatzes
Nonce-basierte CSP Für auf dem Server gerenderte HTML-Seiten, bei denen Sie für jede Antwort ein neues zufälliges Token (Nonce) erstellen können.
Hash-basierte CSP Für statisch bereitgestellte HTML-Seiten oder Seiten, die im Cache gespeichert werden müssen. Dazu gehören beispielsweise Single-Page-Webanwendungen, die mit Frameworks wie Angular oder React erstellt und statisch ohne serverseitiges Rendering bereitgestellt werden.

Schritt 2: Eine strikte CSP festlegen und Skripts vorbereiten

Beim Einrichten einer CSP haben Sie mehrere Möglichkeiten:

  • Modus „Nur Bericht“ (Content-Security-Policy-Report-Only) oder Erzwingungsmodus (Content-Security-Policy). Im Modus „Nur Berichterstellung“ (Content-Security-Policy) blockiert die CSP noch keine Ressourcen – es treten keine Probleme auf. Sie können aber Fehler sehen und Berichte zu den blockierten Inhalten erhalten. Wenn Sie gerade eine CSP einrichten, spielt dies keine Rolle, da in beiden Modi die Fehler in der Browserkonsole angezeigt werden. Wenn überhaupt, ist es mit dem Erzwingungsmodus noch einfacher, blockierte Ressourcen zu sehen und Ihre CSP zu optimieren, da Ihre Seite nicht mehr richtig aussieht. Der Modus "Nur Bericht" wird später am nützlichsten (siehe Schritt 5).
  • Header- oder HTML-<meta>-Tag. Für die lokale Entwicklung ist ein <meta>-Tag möglicherweise praktischer, um Ihre CSP zu optimieren und schnell zu sehen, wie sich dies auf Ihre Website auswirkt. Aber:
    • Wenn Sie Ihre CSP später in der Produktion bereitstellen, wird empfohlen, sie als HTTP-Header festzulegen.
    • Wenn Sie für Ihre CSP den Modus „Nur Bericht“ verwenden möchten, müssen Sie ihn als Header festlegen. Meta-Tags von CSP unterstützen den Modus „Nur Bericht“ nicht.

Option A: Nonce-basierte CSP

Legen Sie in Ihrer Anwendung den folgenden Content-Security-Policy-HTTP-Antwortheader fest:

Content-Security-Policy:
  script-src 'nonce-{RANDOM}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';

Nonce für die CSP generieren

Eine Nonce ist eine Zufallszahl, die nur einmal pro Seitenaufbau verwendet wird. Eine Nonce-basierte CSP kann XSS nur dann abschwächen, wenn der Nonce-Wert von einem Angreifer nicht erraten werden kann. Eine Nonce für CSP muss Folgendes sein:

  • Ein kryptografisch starker Zufallswert (idealerweise mindestens 128 Bits)
  • Wird bei jeder Antwort neu generiert
  • Base64-codiert

Hier sind einige Beispiele, wie Sie eine CSP-Nonce in serverseitigen Frameworks hinzufügen können:

const app = express();

app.get('/', function(request, response) {
  // Generate a new random nonce value for every response.
  const nonce = crypto.randomBytes(16).toString("base64");

  // Set the strict nonce-based CSP response header
  const csp = `script-src 'nonce-${nonce}' 'strict-dynamic'; object-src 'none'; base-uri 'none';`;
  response.set("Content-Security-Policy", csp);

  // Every <script> tag in your application should set the `nonce` attribute to this value.
  response.render(template, { nonce: nonce });
});

<script>-Elementen ein nonce-Attribut hinzufügen

Bei einer Nonce-basierten CSP muss jedes <script>-Element ein nonce-Attribut haben, das mit dem zufälligen Nonce-Wert im CSP-Header übereinstimmt (alle Skripts können dieselbe Nonce haben). Der erste Schritt besteht darin, diese Attribute allen Skripts hinzuzufügen:

Von CSP blockiert
<script src="/path/to/script.js"></script>
<script>foo()</script>
Die CSP blockiert diese Skripts, da sie keine nonce-Attribute haben.

Option B: Hash-basierter CSP-Antwortheader

Legen Sie in Ihrer Anwendung den folgenden Content-Security-Policy-HTTP-Antwortheader fest:

Content-Security-Policy:
  script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';

Für mehrere Inline-Skripts lautet die Syntax: 'sha256-{HASHED_INLINE_SCRIPT_1}' 'sha256-{HASHED_INLINE_SCRIPT_2}'.

Quelldateien dynamisch laden

Alle extern stammenden Skripts müssen dynamisch über ein Inline-Skript geladen werden, weil CSP-Hashes in allen Browsern nur für Inline-Scripts unterstützt werden (Hashes für Quellskripte werden nicht in allen Browsern unterstützt).

Von CSP blockiert
<script src="https://example.org/foo.js"></script>
<script src="https://example.org/bar.js"></script>
Die CSP blockiert diese Skripts, da nur Inline-Skripts gehasht werden können.
Von CSP zugelassen
<script>
  var scripts = [ 'https://example.org/foo.js', 'https://example.org/bar.js'];

  scripts.forEach(function(scriptUrl) {
    var s = document.createElement('script');
    s.src = scriptUrl;
    s.async = false; // to preserve execution order
    document.head.appendChild(s);
  });
</script>
Damit dieses Skript ausgeführt werden kann, muss der Hash des Inline-Skripts berechnet und dem CSP-Antwortheader hinzugefügt werden. Ersetzen Sie dabei den Platzhalter {HASHED_INLINE_SCRIPT}. Wenn Sie die Anzahl der Hashes reduzieren möchten, können Sie optional alle Inline-Skripts zu einem einzigen Skript zusammenführen. Um dies in Aktion zu sehen, kaufen Sie das Beispiel und sehen Sie sich den Code an.

Überlegungen beim Laden von Skripts

Im Code-Snippet oben wird s.async = false hinzugefügt, damit „foo“ vor „bar“ ausgeführt wird, auch wenn „bar“ zuerst geladen wird. In diesem Snippet blockiert s.async = false den Parser nicht, während die Skripts geladen werden. Das liegt daran, dass die Skripts dynamisch hinzugefügt werden. Der Parser wird nur angehalten, wenn die Skripts ausgeführt werden, genau wie bei async-Skripts. Beachten Sie bei diesem Snippet jedoch Folgendes:

  • Eines oder beide Skripts werden möglicherweise ausgeführt, bevor der Download des Dokuments abgeschlossen ist. Wenn das Dokument fertig sein soll, bis die Skripts ausgeführt werden, müssen Sie auf das DOMContentLoaded-Ereignis warten, bevor Sie die Skripts anhängen. Wenn dies zu Leistungseinbußen führt, weil die Skripts nicht früh genug heruntergeladen werden, können Sie weiter oben auf der Seite Tags vorab laden.
  • defer = true tut nichts. Wenn Sie dieses Verhalten benötigen, müssen Sie das Skript zum gewünschten Zeitpunkt manuell ausführen.

Schritt 3: HTML-Vorlagen und clientseitigen Code refaktorieren, um Muster zu entfernen, die mit der CSP nicht kompatibel sind

Zum Ausführen von Skripts können Inline-Event-Handler (z. B. onclick="…", onerror="…") und JavaScript-URIs (<a href="javascript:…">) verwendet werden. Das bedeutet, dass ein Angreifer, der einen XSS-Fehler findet, diese Art von HTML-Code einschleusen und schädlichen JavaScript ausführen kann. Bei einer Nonce- oder Hash-basierten CSP ist die Verwendung eines solchen Markups nicht zulässig. Wenn auf Ihrer Website eines der oben beschriebenen Muster verwendet wird, müssen Sie diese in sicherere Alternativen umwandeln.

Wenn Sie die CSP im vorherigen Schritt aktiviert haben, werden CSP-Verstöße jedes Mal in der Konsole angezeigt, wenn die CSP ein inkompatibles Muster blockiert.

Berichte über CSP-Verstöße in der Chrome-Entwicklerkonsole.

In den meisten Fällen lässt sich das Problem ganz einfach beheben:

Um Inline-Event-Handler zu refaktorieren, schreiben Sie sie so um, dass sie aus einem JavaScript-Block hinzugefügt werden

Von CSP blockiert
<span onclick="doThings();">A thing.</span>
Die CSP blockiert Inline-Event-Handler.
Von CSP zugelassen
<span id="things">A thing.</span>
<script nonce="${nonce}">
  document.getElementById('things').addEventListener('click', doThings);
</script>
Die CSP lässt Event-Handler zu, die über JavaScript registriert sind.

Für javascript:-URIs können Sie ein ähnliches Muster verwenden

Von CSP blockiert
<a href="javascript:linkClicked()">foo</a>
Die CSP blockiert JavaScript-URIs.
Von CSP zugelassen
<a id="foo">foo</a>
<script nonce="${nonce}">
  document.getElementById('foo').addEventListener('click', linkClicked);
</script>
Die CSP lässt Event-Handler zu, die über JavaScript registriert sind.

Verwendung von eval() in JavaScript

Wenn Ihre Anwendung eval() verwendet, um JSON-String-Serialisierungen in JS-Objekte zu konvertieren, sollten Sie solche Instanzen in JSON.parse() refaktorieren, was ebenfalls schneller ist.

Wenn Sie nicht alle Verwendungen von „eval()“ entfernen können, haben Sie trotzdem die Möglichkeit, eine strikte Nonce-basierte CSP festzulegen. Allerdings müssen Sie das CSP-Keyword „'unsafe-eval'“ verwenden, da Ihre Richtlinie dadurch etwas weniger sicher ist.

Diese und weitere Beispiele für eine solche Refaktorierung finden Sie in diesem strengen CSP-Codelab:

Schritt 4 (optional): Fallbacks zur Unterstützung alter Browserversionen hinzufügen

Unterstützte Browser

  • 52
  • 79
  • 52
  • 15,4

Quelle

Wenn ältere Browserversionen unterstützt werden sollen, gehen Sie so vor:

  • Wenn du 'strict-dynamic' verwendest, muss https: als Fallback für alte Versionen von Safari hinzugefügt werden. Dadurch geschieht Folgendes:
    • Alle Browser, die 'strict-dynamic' unterstützen, ignorieren das https:-Fallback. Dadurch wird die Stärke der Richtlinie nicht verringert.
    • In einem alten Browser dürfen extern stammende Skripts nur geladen werden, wenn sie von einem HTTPS-Ursprung stammen. Dies ist weniger sicher als eine strikte CSP – sie ist ein Fallback –, würde aber dennoch bestimmte häufige XSS-Ursachen wie das Einschleusen von javascript:-URIs verhindern, da 'unsafe-inline' nicht vorhanden ist oder in Verbindung mit einem Hash oder einer Nonce ignoriert wird.
  • Um die Kompatibilität mit sehr alten Browserversionen (ab 4 Jahren) zu gewährleisten, können Sie 'unsafe-inline' als Fallback hinzufügen. Alle aktuellen Browser ignorieren 'unsafe-inline', wenn eine CSP-Nonce oder ein CSP-Hash vorhanden ist.
Content-Security-Policy:
  script-src 'nonce-{random}' 'strict-dynamic' https: 'unsafe-inline';
  object-src 'none';
  base-uri 'none';

Schritt 5: CSP bereitstellen

Nachdem Sie bestätigt haben, dass keine legitimen Skripts von der CSP in Ihrer lokalen Entwicklungsumgebung blockiert werden, können Sie mit der Bereitstellung der CSP in Ihrer (Staging, dann) Produktionsumgebung fortfahren:

  1. Optional: Stellen Sie die CSP mit dem Header „Content-Security-Policy-Report-Only“ im Nur-Bericht-Modus bereit. Weitere Informationen zur Reporting API Der reine Berichtsmodus ist praktisch, um eine funktionsgefährdende Änderung wie eine neue CSP in der Produktion zu testen, bevor CSP-Einschränkungen erzwungen werden. Im Modus „Nur Berichterstellung“ hat die CSP keinen Einfluss auf das Verhalten Ihrer Anwendung. Es funktioniert also nichts. Der Browser generiert jedoch weiterhin Konsolenfehler und Berichte zu Verstößen, wenn in der CSP nicht kompatible Muster erkannt werden. So können Sie sehen, welche Probleme für Ihre Endnutzer aufgetreten wären.
  2. Wenn Sie sicher sind, dass Ihre CSP keine Probleme bei Ihren Endnutzern verursacht, stellen Sie sie mit dem Antwortheader Content-Security-Policy bereit. Erst wenn Sie diesen Schritt ausgeführt haben, beginnt die CSP, Ihre Anwendung vor XSS zu schützen. Die CSP-Konfiguration über einen serverseitigen HTTP-Header ist sicherer als das <meta>-Tag. Verwende nach Möglichkeit einen Header.

Beschränkungen

Im Allgemeinen bietet eine strenge CSP eine starke zusätzliche Sicherheitsebene, um XSS zu minimieren. In den meisten Fällen wird die Angriffsfläche durch die CSP erheblich reduziert (gefährliche Muster wie javascript:-URIs werden vollständig deaktiviert). Je nach Art der verwendeten CSP (Nonces, Hashes, mit oder ohne 'strict-dynamic') gibt es jedoch Fälle, in denen die CSP Folgendes nicht schützt:

  • Wenn Sie ein Script, aber eine Injektion direkt in den Text oder in den src-Parameter des <script>-Elements einfügen,
  • Wenn es Einschleusungen in die Speicherorte dynamisch erstellter Skripts gibt (document.createElement('script')), einschließlich in Bibliotheksfunktionen, die script-DOM-Knoten basierend auf dem Wert ihrer Argumente erstellen. Dazu gehören einige gängige APIs wie .html() von jQuery sowie .get() und .post() in jQuery < 3.0.
  • Wenn Vorlagen in alten AngularJS-Anwendungen eingeschleust werden Ein Angreifer, der eine AngularJS-Vorlage einschleusen kann, kann damit beliebigen JavaScript-Code ausführen.
  • Wenn die Richtlinie „'unsafe-eval'“ enthält, werden Injektionen in eval(), setTimeout() und einige andere selten verwendete APIs durchgeführt.

Entwickler und Sicherheitsingenieure sollten auf solche Muster bei Codeüberprüfungen und Sicherheitsaudits besonders achten. Weitere Details zu den oben beschriebenen Fällen findest du in dieser CSP-Präsentation.

Weitere Informationen