Content-Security-Policy teilt dem Browser mit, womit deine Seite interagieren soll. Dadurch kann der Browser stoppen, wenn etwas auf deiner Seite (möglicherweise etwas, das böswillig etwa über einen XSS-Angriff eingeschleust wird) versucht, mit etwas anderem zu interagieren.
Der Inhalt dieses Artikels richtet sich an Entwickler:innen mit technischem Hintergrund, um Fullstory in einer Umgebung zum Laufen zu bringen, in der CSP erzwungen wird. Es ist nicht beabsichtigt, Best Practices dafür vorzuschreiben, wie deine Website die Sicherheit in Bezug auf CSP angehen sollte.
Zusätzlich zu den Berechtigungen, die deine Website ohne Fullstory benötigt, gibt es CSP-Direktiven, die du in Fullstory einschließen musst.
-
connect-src: https://edge.fullstory.com https://rs.fullstory.com
script-src: https://edge.fullstory.com https://rs.fullstory.com
img-src: https://rs.fullstory.com
Wir benötigen connect-src
, um Verbindungen von den Browsern deiner Besucher:innen zu unseren Servern herzustellen, um Berichte über die Seiten zu erstellen, die die Besucher:innen verwenden, und um die Ereignisse zu melden, die auf diesen Seiten stattfinden. Es gibt drei Codeabschnitte, die von Fullstory verwendet werden:
-
das Datenerfassungs-Snippet, das das Datenerfassungsskript (fs.js) herunterlädt;
-
das Datenerfassungsskript fs.js;
-
Code für alle Integrationen, die du möglicherweise mit Fullstory verwendest.
Jede davon erfordert etwas Arbeit, um sie in deine Website aufzunehmen.
CSPv2- und Script-Src-Hashes
In seiner ersten Version erlaubt CSP entweder keine Inline-Skripte (d. h. Skripte, die auf irgendeine Weise geladen werden, außer <script src=”www.somewhere.com/path/to/some.js”/>
) oder es erlaubt alle Inline-Skripte, was aufgrund von XSS und ähnlichen Injection-Angriffen ein Problem darstellt. In der zweiten Version von CSP kannst du jedoch angeben, welche Skripts du inline zulassen möchtest. Da das Web so ist, wie es ist, unterstützen natürlich noch nicht alle Browser CSPv2.
Wir ziehen es vor, dass das Datenerfassungs-Snippet inline ist. Wir bevorzugen es, weil es früher mit der Datenerfassung beginnt (kein Warten auf das separate Laden des Snippets). Außerdem bedeutet die Nicht-Inline-Ausführung, dass du entweder auf ein synchrones Laden warten (was deine Website verlangsamt) oder du zusätzliche Arbeit leisten musst, um zu wissen, wann du unsere API-Aufrufe wie FS.identify() verwenden kannst. Ein Inline-Snippet löst beide Probleme – aber du brauchst CSPv2, um es sicher zu machen.
Um CSPv2 zu verwenden, musst du nur einen Hash zur CSP-Direktive script-src oben hinzufügen, etwa so:
script-src: https://edge.fullstory.com https://rs.fullstory.com '[HIER HASH EINFÜGEN]'
** Dieser Hash ist ein ungültiges Beispiel; nicht kopieren und einfügen.
Welche Option für dich die Richtige ist, hängt von deinem Snippet ab, da dieses deine orgId enthält, sodass es für alle unsere Kund:innen unterschiedlich sein wird. (Der Hash ändert sich auch, wenn du eine abschließende Leerzeile oder zusätzliche Leerzeichen hast, sei also vorsichtig.) Es gibt Websites, die es für dich generieren, wenn du das Snippet in die Website einfügst, und es kann auch mit dem OpenSSL-Befehl generiert werden
echo -n "snippet value" | openssl dgst -sha256 -binary | openssl enc -base64
Beachte, dass der „snippet value“ alles zwischen den <script>-Tags auf der Einstellungsseite deiner Organisation ist. Du solltest die Tags nicht in den Hash-Wert aufnehmen.
Die einfachste Methode besteht möglicherweise darin, (in der Entwicklung) einen CSP zu verwenden, der nicht über den Hash verfügt, und die Browserkonsole wird dir den richtigen Wert mitteilen. Beispielsweise siehst du eine Meldung wie:
Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self'". Either the 'unsafe-inline' keyword, a hash ('sha256-ZznlC9tUx8IcYnPzR8RnQYigA4/Vzq86uY90/LM0qcI='), or a nonce ('nonce-...') is required to enable inline execution.
In diesem Fall kann der Hash (sha256-ZznlC9tUx8IcYnPzR8RnQYigA4/Vzq86uY90/LM0qcI=
) als Teil der Direktive script-src
eingefügt werden.
Auch hier unterstützen noch nicht alle Browser CSPv2. Sich darauf zu verlassen bedeutet, dass alte Versionen von Safari, jede Version von Internet Explorer und einige mobile oder selten verwendete Browser ungeschützt sind. Dennoch ist dies ein gutes Gleichgewicht zwischen Schutz und Benutzerfreundlichkeit, und wir erwarten, dass dies immer weniger Benutzer:innen betreffen wird, also könnte es eine gute Lösung sein.
CSPv1 und „unsafe-inline“
Wenn du dich nicht auf CSPv2 verlassen möchtest, kannst du immer eine CSPv1 script-src
mit unsafe-inline
verwenden, obwohl dies nicht das ist, was wir empfehlen. Dennoch kann es schwierig sein, Inline-Skripte loszuwerden, wenn du deine Website damit gestartet hast. Wenn du also bereits unsafe-inline
benötigst, kannst du ein Inline-Fullstory-Snippet verwenden, ohne eine neue Schwachstelle hinzuzufügen. Und wenn deine anderen CSP-Direktiven streng sind, selbst wenn du davon ausgehst, dass irgendeine Art von Injektion ein „schlechtes“ Inline-Skript auf deine Seite gebracht hat, können die anderen Direktiven jegliche Exfiltration als Ergebnis oder die Injektion großer oder veränderbarer Skripts über ein <script src=.../>
-Tag verhindern. Dies ist also zumindest eine einfache Möglichkeit, viele der Vorteile von CSP zu nutzen und deine Exposition zu begrenzen. Und es ist super einfach; füge einfach script-src
und die connect-src
Direktive hinzu, und du musst dich um nichts anderes kümmern.
In diesem Szenario sieht das script-src
Tag so aus:
script-src: https://edge.fullstory.com https://rs.fullstory.com 'unsafe-inline'
(Beachte, dass die Unterstützung für CSPv1 zwar weiter verbreitet ist als CSPv2, aber immer noch nicht universell ist. Beispielsweise unterstützen Internet Explorer 11 und niedriger nur die sandbox
-Direktive.)
CSPv1 ohne „unsafe-inline“
Wenn du CSPv2 nicht verwenden kannst und nicht bereit bist, das Keyword unsafe-inline
zu verwenden, hast du immer noch eine Option, obwohl sie natürlich die komplizierteste ist.
Dein CSP-Header wird zusätzlich zu allem, was deine Website sonst noch benötigt, Folgendes enthalten:
-
connect-src: https://edge.fullstory.com https://rs.fullstory.com
-
script-src: https://edge.fullstory.com https://rs.fullstory.com 'self'
Die Komplikation ist nicht das, was du in deinen CSP-Header selbst einfügst, sondern was du mit deinem Datenerfassungs-Snippet machst. Das wird entweder in deinen eigenen JavaScript-Code eingebettet, den du bereits von einer Quell-URL laden musst, oder es wird z. B. durch Folgendes ersetzt:
<script src=”https://my.site.com/fs-snippet.js”/ async>
Das Keyword async
ist wichtig; das hält Fullstory davon ab, das Laden deiner Seite zu verlangsamen. Und diese fs-snippet.js
URL wird das Fullstory-Snippet von deiner Einstellungsseite in unserer Benutzeroberfläche ausgeben (oder hoffentlich von deinen Webseiten, die bereits Fullstory verwenden). Allein dadurch wird Fullstory ohne Inline-Skript nur geringfügig langsamer geladen, als wenn es inline wäre.
Wenn deine Website über eigenen Code verfügt, kannst du das Snippet direkt in dein Skript einfügen. Wir haben es so geschrieben, dass es auch dann sicher ist, wenn du beides tust und irgendwo zwei Kopien auf einer Seite bekommst. Wenn du das Snippet nicht in dein eigenes Skript einfügst, musst du möglicherweise nur ein kleines bisschen hinzufügen, damit du z. B.FS.identify()
jederzeit aufrufen kannst:
…normales Datenerfassungs-Snippet hier… if (window[„_fs_loaded“]) { window[“_fs_loaded”](); }
Diese drei zusätzlichen Zeilen sind nützlich, damit du an anderer Stelle in deinem Code window[_fs_loaded]
als eine Funktion definieren kannst, die FS.identify()
erst dann aufruft, wenn das Snippet das FS-Namespace-Objekt und die verschiedenen Funktionen darin definiert hat, etwa so:
if (window.FS) { FS.identify(strUserID); } else { window[“_fs_loaded”] = function() { FS.identify(strUserID); }; }
Du benötigst dies, da du aufgrund des asynchronen Ladens des Snippets nicht wissen kannst, ob dein Code vor oder nach dem Snippet ausgeführt wird. Wenn das Snippet zuerst geladen wird und danach dein Code, wird der direkte Aufruf von FS.identify()
ausgeführt. Wenn dein Code zuerst geladen wird und FS mit seinen Funktionen noch nicht verfügbar ist, wird ein Aufruf von FS.identify()
in window[_fs_loaded]
gespeichert und später ausgeführt. (Wenn erforderlich kannst du eine ausgefallenere Version erstellen, die mehrere Aufrufe verkettet, oder _fs_loaded
zu einem Array und einer Schleife machen, sodass alle darin enthaltenen Funktionen aufgerufen werden).
Wenn du Fragen hast, kannst du dich gerne an uns wenden. Wir helfen gerne weiter.
Abschließende Gedanken
Denke daran, dass es immer gut ist, deinen vorgeschlagenen CSP zuerst mit einem Content-Security-Policy-Report-Only
-Header zu testen. Wenn du bereits etwas mit CSP tust, es aber verschärfen möchtest (z. B. um unsafe-inline
zu beseitigen), kannst du eine „neue“ Richtlinie im CSPRO-Header bereitstellen und gleichzeitig eine „alte“ Richtlinie mit Content-Security-Policy
durchsetzen. Du musst aber auf jeden Fall sicherstellen, dass der Report-only-Header keine Berichte generiert, bevor du beginnst, ihn durchzusetzen und deine Website versehentlich zu beschädigen!
Wenn du CSPv2 verwendest, ändern sich die Hashes gelegentlich und du musst deine Richtlinien aktualisieren. Aber das Snippet befindet sich unter deiner Kontrolle, sodass du weißt, dass du das CSP-Update jedes Mal durchführen musst, wenn du dein Snippet aktualisierst, und abgesehen davon wird es stabil bleiben.
Verweise
https://content-security-policy.com/ enthält eine ziemlich nützliche Beschreibung der verschiedenen CSP-Versionen, einschließlich der Frage, welche Browser was unterstützen. Es enthält auch ein Browser-Testtool, um zu sehen, was dein Browser tatsächlich zulässt (vorausgesetzt, du hast Zugriff auf den gewünschten Browser).
https://www.w3.org/TR/CSP2/ ist die offizielle W3C-Kandidatenempfehlung für CSPv2.
https://developer.mozilla.org/en-US/docs/Web/Security/CSP ist eigentlich die Spezifikation für CSPv1. (https://www.w3.org/TR/CSP1/ ist ein abgebrochener W3C-Entwurf zur Kodifizierung von CSPv1, der jedoch eingestellt wurde, da sie stattdessen zu v2 übergingen.)
Es gibt Dutzende von SHA256-Generatoren; einer ist unter http://www.xorbin.com/tools/sha256-hash-calculator abrufbar. Bitte denke daran, die Leerzeichen in deiner Eingabe exakt beizubehalten, da dies die Ausgabe verändert!
FAQ
Ich habe eine EU-Organisation. Wie sollten meine URLs formatiert sein?
Kund:innen mit einer EU-Organisation müssen eu1 wie folgt in die aufgelisteten URLs aufnehmen:
-
connect-src: https://edge.eu1.fullstory.com https://rs.eu1.fullstory.com
script-src: https://edge.eu1.fullstory.com https://rs.eu1.fullstory.com
img-src: https://rs.eu1.fullstory.com