Browser Support
Cross-Site-Scripting (XSS), die Möglichkeit, schädliche Scripts in eine Web-App einzuschleusen, ist seit über einem Jahrzehnt eine der größten Sicherheitslücken im Web.
Die Content Security Policy (CSP) ist eine zusätzliche Sicherheitsebene, die dazu beiträgt, XSS-Angriffe zu verhindern. Um eine CSP zu konfigurieren, fügen Sie einer Webseite den HTTP-Header Content-Security-Policy hinzu und legen Sie Werte fest, mit denen gesteuert wird, welche Ressourcen der User-Agent für diese Seite laden kann.
Auf dieser Seite wird erläutert, wie Sie eine CSP auf Grundlage von Nonces oder Hashes verwenden, um XSS zu minimieren. Dies ist eine Alternative zu den häufig verwendeten Host-Zulassungslisten-basierten CSPs, die die Seite oft anfällig für XSS machen, da sie in den meisten Konfigurationen umgangen werden können.
Wichtiger Begriff: Eine Nonce ist eine Zufallszahl, die nur einmal verwendet wird. Sie können damit ein <script>-Tag als vertrauenswürdig kennzeichnen.
Schlüsselbegriff: Eine Hash-Funktion ist eine mathematische Funktion, die einen Eingabewert in einen komprimierten numerischen Wert, einen Hash, umwandelt. Sie können einen Hash (z. B. SHA-256) verwenden, um ein Inline-<script>-Tag als vertrauenswürdig zu kennzeichnen.
Eine Content Security Policy, die auf Nounces oder Hashes basiert, wird oft als strikte CSP bezeichnet. Wenn eine Anwendung eine strenge CSP verwendet, können Angreifer, die HTML-Injektionslücken finden, diese in der Regel nicht nutzen, um den Browser zu zwingen, schädliche Scripts in einem anfälligen Dokument auszuführen. Das liegt daran, dass bei Strict CSP nur gehashte Scripts oder Scripts mit dem richtigen Nonce-Wert, der auf dem Server generiert wurde, zulässig sind. Angreifer können das Script also nicht ausführen, ohne die richtige Nonce für eine bestimmte Antwort zu kennen.
Warum sollten Sie eine strenge CSP verwenden?
Wenn Ihre Website bereits einen CSP hat, der so aussieht: script-src www.googleapis.com, ist er wahrscheinlich nicht effektiv gegen websiteübergreifende Angriffe. Diese Art von CSP wird als CSP für die Zulassungsliste bezeichnet. Sie erfordern viele Anpassungen und können von Angreifern umgangen werden.
Strenge CSPs, die auf kryptografischen Nounces oder Hashes basieren, vermeiden diese Fallstricke.
Strikte CSP-Struktur
Eine einfache strenge Content Security Policy verwendet einen der folgenden HTTP-Antwortheader:
Strikte CSP auf Grundlage von Nonces
Content-Security-Policy:
script-src 'nonce-{RANDOM}' 'strict-dynamic';
object-src 'none39;;
base-uri 'none';
Hashbasierte strikte CSP
Content-Security-Policy:
script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic';
object-src 'none39;;
base-uri 'none';
Die folgenden Eigenschaften machen ein CSP wie dieses „streng“ und damit sicher:
- Dabei werden Nounces
'nonce-{RANDOM}'oder Hashes'sha256-{HASHED_INLINE_SCRIPT}'verwendet, um anzugeben, welchen<script>-Tags der Entwickler der Website vertraut, dass sie im Browser des Nutzers ausgeführt werden. - Damit wird
'strict-dynamic'festgelegt, um den Aufwand für die Bereitstellung einer nonce- oder hashbasierten CSP zu verringern, indem die Ausführung von Skripts, die von einem vertrauenswürdigen Skript erstellt werden, automatisch zugelassen wird. Dadurch wird auch die Verwendung der meisten JavaScript-Bibliotheken und ‑Widgets von Drittanbietern ermöglicht. - Sie basiert nicht auf Zulassungslisten für URLs und ist daher nicht anfällig für gängige CSP-Umgehungen.
- Es werden nicht vertrauenswürdige Inline-Skripts wie Inline-Event-Handler oder
javascript:-URIs blockiert. - Sie schränkt
object-srcein, um gefährliche Plug-ins wie Flash zu deaktivieren. - Dadurch wird
base-urieingeschränkt, um das Einfügen von<base>-Tags zu blockieren. Dadurch wird verhindert, dass Angreifer die Speicherorte von Skripts ändern, die über relative URLs geladen werden.
Strikte CSP verwenden
Wenn Sie einen strengen CSP verwenden möchten, müssen Sie Folgendes tun:
- Entscheiden Sie, ob Ihre Anwendung eine nonce- oder hashbasierte CSP festlegen soll.
- Kopieren Sie die CSP aus dem Abschnitt Strikte CSP-Struktur und legen Sie sie als Antwortheader für Ihre gesamte Anwendung fest.
- Refaktorieren Sie HTML-Vorlagen und clientseitigen Code, um Muster zu entfernen, die nicht mit CSP kompatibel sind.
- Stellen Sie Ihren CSP bereit.
Sie können während dieses Prozesses den Lighthouse-Audit Best Practices (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 ist, um XSS zu verhindern.
Schritt 1: Entscheiden, ob Sie ein nonce- oder hashbasiertes CSP benötigen
So funktionieren die beiden Arten von strenger CSP:
Nonce-basiertes CSP
Bei einer nonce-basierten CSP generieren Sie zur Laufzeit eine Zufallszahl, die Sie in Ihre CSP aufnehmen und jedem Script-Tag auf Ihrer Seite zuordnen. Ein Angreifer kann kein schädliches Skript auf Ihrer Seite einfügen oder ausführen, da er die richtige Zufallszahl für dieses Skript erraten müsste. Das funktioniert nur, wenn die Zahl nicht erraten werden kann und bei jeder Antwort zur Laufzeit neu generiert wird.
Verwenden Sie eine nonce-basierte CSP für HTML-Seiten, die auf dem Server gerendert werden. Für diese Seiten können Sie für jede Antwort eine neue Zufallszahl erstellen.
Hashbasierte CSP
Bei einem hashbasierten CSP wird der Hash jedes Inline-Script-Tags dem CSP hinzugefügt. Jedes Script hat einen anderen Hash. Ein Angreifer kann kein schädliches Script in Ihre Seite einfügen oder ausführen, da der Hash dieses Scripts in Ihrem CSP enthalten sein müsste, damit es ausgeführt werden kann.
Verwenden Sie einen hashbasierten CSP für statisch bereitgestellte HTML-Seiten oder Seiten, die im Cache gespeichert werden müssen. Sie können beispielsweise eine hashbasierte CSP für Single-Page-Webanwendungen verwenden, die mit Frameworks wie Angular, React oder anderen erstellt wurden und statisch ohne serverseitiges Rendering bereitgestellt werden.
Schritt 2: Strengen CSP festlegen und Skripts vorbereiten
Beim Festlegen eines CSP haben Sie mehrere Optionen:
- Nur-Bericht-Modus (
Content-Security-Policy-Report-Only) oder Erzwingungsmodus (Content-Security-Policy). Im Nur-Bericht-Modus werden Ressourcen noch nicht blockiert. Daher wird nichts auf Ihrer Website beschädigt. Sie können jedoch Fehler sehen und Berichte für alles erhalten, was blockiert worden wäre. Lokal spielt das beim Festlegen des CSP keine Rolle, da die Fehler in beiden Modi in der Browserkonsole angezeigt werden. Der Erzwingungsmodus kann Ihnen helfen, Ressourcen zu finden, die durch Ihre CSP-Version im Entwurf blockiert werden, da das Blockieren einer Ressource dazu führen kann, dass Ihre Seite nicht richtig angezeigt wird. Der Modus „Nur Bericht“ ist später im Prozess am nützlichsten (siehe Schritt 5). - Header oder HTML-Tag
<meta>. Für die lokale Entwicklung kann ein<meta>-Tag praktischer sein, um den CSP anzupassen und schnell zu sehen, wie er sich auf Ihre Website auswirkt. Es gilt allerdings:- Wenn Sie Ihre CSP später in der Produktion bereitstellen, empfehlen wir, sie als HTTP-Header festzulegen.
- Wenn Sie Ihre CSP im Report-Only-Modus festlegen möchten, müssen Sie sie als Header festlegen, da CSP-Meta-Tags den Report-Only-Modus nicht unterstützen.
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 CSP generieren
Eine Nonce ist eine Zufallszahl, die nur einmal pro Seitenaufbau verwendet wird. Eine noncebasierte CSP kann XSS nur dann verhindern, wenn Angreifer den Nonce-Wert nicht erraten können. Eine CSP-Nonce muss:
- Ein kryptografisch starker Zufallswert (idealerweise mit einer Länge von mindestens 128 Bit)
- Wird für jede Antwort neu generiert
- Base64-codiert
Hier sind einige Beispiele für das Hinzufügen eines CSP-Nonce in serverseitigen Frameworks:
- Django (Python)
- Express (JavaScript):
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(&>quot;Content-Security-Policy", csp); // Every script tag in your application should set the `nonce` attribute to this value. response.render(template, { nonce: nonce }); });
nonce-Attribut zu <script>-Elementen 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. Als Erstes müssen Sie diese Attribute allen Skripts hinzufügen, damit sie von der CSP zugelassen werden.
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';
Bei mehreren Inline-Scripts lautet die Syntax so:
'sha256-{HASHED_INLINE_SCRIPT_1}' 'sha256-{HASHED_INLINE_SCRIPT_2}'.
Quellskripts dynamisch laden
Sie können Drittanbieterskripts dynamisch über ein Inline-Skript laden.
<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.hea<d.appen>dChild(s);
});
/script{HASHED_INLINE_SCRIPT}. Um die Anzahl der Hashes zu reduzieren, können Sie alle Inline-Scripts in einem einzigen Script zusammenführen. Beispiel und Code
<script src="https://example.org/fo><o.js&qu>o<t;/script script src="https://exam><ple.org>/bar.js"/script
integrity-Attribut haben, das einer zulässigen Quelle entspricht.
Überlegungen zum Laden von Skripts
Im Beispiel für Inline-Skripts wird s.async = false hinzugefügt, damit foo vor bar ausgeführt wird, auch wenn bar zuerst geladen wird. In diesem Snippet wird der Parser durch s.async = false nicht blockiert, während die Skripts geladen werden, da die Skripts dynamisch hinzugefügt werden. Der Parser wird nur während der Ausführung der Skripts angehalten, wie bei async-Skripts. Beachten Sie jedoch Folgendes:
-
Eines oder beide Skripts werden möglicherweise ausgeführt, bevor das Dokument vollständig heruntergeladen wurde. Wenn das Dokument fertig sein soll, bevor die Skripts ausgeführt werden, warten Sie auf das
DOMContentLoaded-Ereignis, bevor Sie die Skripts anhängen. Wenn dies zu Leistungsproblemen führt, weil die Skripts nicht früh genug heruntergeladen werden, verwenden Sie Preload-Tags weiter oben auf der Seite. -
defer = truehat keine Auswirkungen. Wenn Sie dieses Verhalten benötigen, führen Sie das Skript manuell aus, wenn es erforderlich ist.
Schritt 3: HTML-Vorlagen und clientseitigen Code umgestalten
Inline-Ereignis-Handler (z. B. onclick="…", onerror="…") und JavaScript-URIs (<a href="javascript:…">) können zum Ausführen von Skripts verwendet werden. Das bedeutet, dass ein Angreifer, der einen XSS-Bug findet, diese Art von HTML einschleusen und schädliches JavaScript ausführen kann. Eine nonce- oder hashbasierte CSP verbietet die Verwendung dieser Art von Markup.
Wenn auf Ihrer Website eines dieser Muster verwendet wird, müssen Sie es in eine sicherere Alternative umgestalten.
Wenn Sie im vorherigen Schritt CSP aktiviert haben, sehen Sie in der Konsole jedes Mal CSP-Verstöße, wenn CSP ein inkompatibles Muster blockiert.
In den meisten Fällen ist die Behebung ganz einfach:
Inline-Event-Handler umgestalten
<span id="th>ings&quo<t;A t>h<ing./span
script nonce=>"${nonce}"
document.getElementById('things').addEventL<istener>('click', doThings);
/script<span onclick="doThing>s();&quo<t;A t>hing./span
javascript:-URIs refaktorieren
<a id=">;fo<o&>q<uot;foo/a
script nonce=>"${nonce}"
document.getElementById('foo').addEventList<ener(>39;click', linkClicked);
/script<a href="javascript:linkClick>ed(<)&>quot;foo/a
eval() aus Ihrem JavaScript entfernen
Wenn Ihre Anwendung eval() verwendet, um JSON-String-Serialisierungen in JS-Objekte zu konvertieren, sollten Sie solche Instanzen in JSON.parse() umgestalten, was auch schneller ist.
Wenn Sie nicht alle Verwendungen von eval() entfernen können, können Sie trotzdem eine strenge nonce-basierte CSP festlegen. Sie müssen jedoch das CSP-Schlüsselwort 'unsafe-eval' verwenden, wodurch Ihre Richtlinie etwas weniger sicher ist.
Weitere Beispiele für solche Refactorings finden Sie in diesem Codelab für strikte CSP:
Schritt 4 (optional): Fallbacks hinzufügen, um alte Browserversionen zu unterstützen
Browser Support
Wenn Sie ältere Browserversionen unterstützen müssen:
- Wenn Sie
strict-dynamicverwenden, müssen Siehttps:als Fallback für ältere Safari-Versionen hinzufügen. Dabei gilt:- Alle Browser, die
strict-dynamicunterstützen, ignorieren denhttps:-Fallback. Die Stärke der Richtlinie wird dadurch also nicht verringert. - In alten Browsern können extern bezogene Scripts nur geladen werden, wenn sie von einem HTTPS-Ursprung stammen. Diese Methode ist weniger sicher als ein strenger CSP, verhindert aber dennoch einige häufige XSS-Ursachen wie das Einfügen von
javascript:-URIs.
- Alle Browser, die
- Um die Kompatibilität mit sehr alten Browserversionen (älter als 4 Jahre) zu gewährleisten, können Sie
unsafe-inlineals Fallback hinzufügen. Alle aktuellen Browser ignorierenunsafe-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 9;none';
base-uri 'none';
Schritt 5: CSP bereitstellen
Nachdem Sie bestätigt haben, dass Ihr CSP keine legitimen Skripts in Ihrer lokalen Entwicklungsumgebung blockiert, können Sie es in der Staging-Umgebung und dann in der Produktionsumgebung bereitstellen:
- Optional: Stellen Sie Ihren CSP im Modus „Nur Bericht“ mit dem
Content-Security-Policy-Report-Only-Header bereit. Der Modus „Nur Bericht“ ist nützlich, um eine potenziell schwerwiegende Änderung wie einen neuen CSP in der Produktion zu testen, bevor Sie CSP-Einschränkungen erzwingen. Im Nur-Bericht-Modus wirkt sich Ihr CSP nicht auf das Verhalten Ihrer App aus. Der Browser generiert jedoch weiterhin Konsolenfehler und Berichte zu Verstößen, wenn er auf Muster stößt, die mit Ihrem CSP inkompatibel sind. So können Sie sehen, was für Ihre Endnutzer nicht funktioniert hätte. Weitere Informationen finden Sie unter Reporting API. - Wenn Sie sicher sind, dass Ihr CSP die Website für Ihre Endnutzer nicht beeinträchtigt, stellen Sie ihn mit dem Antwortheader
Content-Security-Policybereit. Wir empfehlen, das CSP serverseitig über einen HTTP-Header festzulegen, da dies sicherer ist als ein<meta>-Tag. Nachdem Sie diesen Schritt abgeschlossen haben, schützt Ihr CSP Ihre App vor XSS.
Beschränkungen
Eine strenge CSP bietet in der Regel eine zusätzliche Sicherheitsebene, die dazu beiträgt, XSS zu minimieren. In den meisten Fällen wird die Angriffsfläche durch CSP erheblich reduziert, indem gefährliche Muster wie javascript:-URIs abgelehnt werden. Je nach Art des verwendeten CSP (Nonces, Hashes, mit oder ohne 'strict-dynamic') gibt es jedoch Fälle, in denen CSP Ihre App nicht so gut schützt:
- Wenn Sie ein Script mit einem Nonce versehen, aber eine Injektion direkt in den Body oder den
src-Parameter dieses<script>-Elements erfolgt. - Wenn es Injections in die Speicherorte dynamisch erstellter Skripts (
document.createElement('script')) gibt, einschließlich in alle Bibliotheksfunktionen, diescript-DOM-Knoten basierend auf den Werten ihrer Argumente erstellen. Dazu gehören einige gängige APIs wie.html()von jQuery sowie.get()und.post()in jQuery < 3.0. - Wenn in alten AngularJS-Anwendungen Template-Injections vorhanden sind. Ein Angreifer, der in eine AngularJS-Vorlage einschleusen kann, kann sie verwenden, um beliebigen JavaScript-Code auszuführen.
- Wenn die Richtlinie
'unsafe-eval'enthält, werden Injections ineval(),setTimeout()und einige andere selten verwendete APIs erkannt.
Entwickler und Sicherheitsingenieure sollten bei Code-Reviews und Sicherheitsprüfungen besonders auf solche Muster achten. Weitere Informationen zu diesen Fällen finden Sie unter Content Security Policy: A Successful Mess Between Hardening and Mitigation.