Content-Security-Policy (CSP)
The CSP response header restricts what resources are allowed to execute, but can sometimes be bypassed
Last updated
The CSP response header restricts what resources are allowed to execute, but can sometimes be bypassed
Last updated
A more modern protection against XSS and some other attacks is the . This is a Header (Content-Security-Policy:
) or <meta>
value in a response that tells the browser what should be allowed, and what shouldn't. An important directive that can be set using this header is , defining where JavaScript code may come from:
You can always find the currently applied CSP by opening the DevTools in Chromium, then navigating to Application and scroll down to top in order to find all affecting headers or meta tags and their rules:
A different less-common way to allow inline scripts without allowing all inline scripts is with nonces, random values generated by the server. This nonce is put inside of the script-src
directive like 'nonce-2726c7f26c'
, requiring every inline script to have a nonce=
attribute equaling the specified random value. In theory, an attacker should not be able to predict this random value as it should be different for every request. This works in a similar way to CSRF tokens and relies on secure randomness by the server. If implemented well, this is a very effective way of preventing XSS.
The last important string in this directive is 'unsafe-eval'
which is disabled by default, blocking several functions that can execute code from a string:
eval()
Function()
Passing a string to setTimeout()
, setInterval()
or window.setImmediate()
(for example: setTimeout("alert()", 500)
)
Note however that this does not prevent all methods of executing code from a string. If 'unsafe-inline'
allows it, you can still write to the DOM with event handlers if required:
It also doesn't deny the location =
sink, removing 'unsafe-inline'
is needed to prevent this:
To easily evaluate and find problems with a CSP header, you can use Google's CSP Evaluator which tells you for every directive what potential problems it finds:
This directive defines which hosts can be connected to, meaning if your attacker's server is not on the list, you cannot make a fetch()
request like normal to your server in order to exfiltrate any data. While there is no direct bypass for this, you may be able to still connect to any origin allowed to exfiltrate data by storing it, and later retrieving it as the attacker at a place you can find. By #forcing-requests-fetch, you could, for example, make a POST request that changes a profile picture, or some other public data, while embedding the data you want to exfiltrate. This way the policy is not broken, but the attacker can still find the data on the website itself.
With this technique, remember that even one bit of information is enough, as you can often repeat it to reveal a larger amount of information.
A more general bypass for this is to redirect the user fully using JavaScript, as browsers do not prevent this. Then in the URL, you put the data you want to exfiltrate to receive it in a request:
Then we use the WebRTC trick to exfiltrate any data over DNS:
'self'
The URLs and 'self'
trust all scripts coming from that domain, meaning in a secure environment no user data should be stored under those domains, like uploaded JavaScript files. If this is allowed, an attacker can simply upload and host their payload on an allowed website and it is suddenly trusted by the CSP.
For more complex scenarios where you cannot directly upload .js
files, the Content-Type:
header comes into play. The browser decides based on this header if the requested file is likely to be a real script, and if the type is image/png
for example, it will simply refuse to execute it:
An application may sanitize uploaded files by checking for a few signatures if it looks like a valid PNG, JPEG, GIF, etc. file which can limit exploitability as it still needs to be valid JavaScript code without SyntaxError
s. In these cases, you can try to make a "polyglot" that passes the validation checks of the server, while remaining valid JavaScript by using the file format in a smart way and language features like comments to remove unwanted code.
script-src
(AngularJS Bypass + JSONP)Loading any of these blocks in a CSP that allows it, will trigger the alert(document.domain)
function. A common pattern for finding these bypasses is using Angular to create an environment where code can be executed from event handlers, and then another library or callback function to click on the element, triggering the handler with your malicious code.
See #angularjs for more complex AngularJS injections that bypass filters. Also, note that other frameworks such as #vuejs or #htmx may allow similar bypasses if they are accessible when unsafe-eval
is set in the CSP.
More script gadgets for different frameworks are shown in the presentation below. This includes: Knockout, Ajaxify, Bootstrap, Google Closure, RequireJS, Ember, jQuery, jQuery Mobile, Dojo Toolkit, underscore, Aurelia, Polymer 1.x, AngularJS 1.x and Ractive.
URL's in a CSP may be absolute, not just an origin. The following example provides a full URL to base64.min.js
, and you would expect only that script could be loaded from the cdn.js.cloudflare.com
origin.
This is not entirely true, however. If another origin, like 'self'
contains an Open Redirect vulnerability, you may redirect a script URL to any path on cdnjs.cloudflare.com
!
base-src
If a CSP filters scripts based on a nonce, and does not specify a base-src
directive, you may be able to hijack relative URLs after your injection point.
Let's say the target page with an HTML-injection looks as follows:
The relative <script>
tag can be redirect to another domain using the <base>
tag as follows:
Now, the script with a valid nonce is loaded from https://attacker.com/script.js
instead of the target website!
With the above policy set, any <script src=...>
that is not from the current domain or "example.com" will be blocked. When you explicitly set a policy like this it also disables inline scripts like <script>alert()</script>
or event handlers like <style onload=alert()>
from executing, even ones from the server itself as there is no way to differentiate between intended and malicious. This possibly breaking change where all scripts need to come from trusted URLs is sometimes "fixed" by adding a special string that allows inline script tags and event handlers to execute, which as the name suggests, is very unsafe.
Another useful method is WebRTC which bypasses connect-src
. The DNS lookup is not blocked and allows for dynamically inserting data into the subdomain field. These names are case-insensitive so an encoding scheme like Base32 can be used to exfiltrate arbitrary data (max ~100 characters per request). Using it is easy to set up a domain to exfiltrate from:
Finally, we receive DNS requests on the interactsh-client
that we can :
Some more ambiguous types are allowed, however, like text/plain
, text/html
or no type at all. These are especially useful as commonly a framework will decide what Content-Type
to add based on the file extension, which may be empty in some cases causing it to choose a type allowed for JavaScript execution. This ambiguity is prevented however with an extra
X-Content-Type-Options: nosniff
header that is sometimes set, making the detection from the browser a lot more strict and only allowing real application/javascript
files ().
Another idea instead of storing data, is reflecting data. If there is any page that generates a response you can turn into valid JavaScript code, you may be able to abuse it for your payload. or other callback endpoints are also useful here as they always have the correct Content-Type
, and may allow you to insert arbitrary code in place of the ?callback=
parameter, serving as your reflection of valid JavaScript code.
Every origin in this directive is trusted with all URLs it hosts. A common addition here is CDN (Content Delivery Network) domains that host many different JavaScript files for libraries. While in very unrestricted situations a CDN like will host every file on NPM, even malicious ones, others are less obvious.
The or domains for example host only specific popular libraries which should be secure, but some have exploitable features. The most well-known is AngularJS, which a vulnerable site may also host themselves removing the need for a CDN. This library searches for specific patterns in the DOM that can define event handlers without the regular inline syntax. This bypasses the CSP and can allow arbitrary JavaScript execution by loading such a library, and including your own malicious content in the DOM:
See for a not-so-updated list of public JSONP endpoints you may find useful.
The following script would be allowed by the , note that the angular.js
path is not normally allowed, but it is through the redirect because its origin is allowed. This can be abused with some HTML that executes arbitrary JavaScript, even if 'unsafe-eval'
is not set:
image/png
file as JavaScript source