7 minute read

Todays adventures take us down a rabbit hole I see pop up almost constantly in my testing; which developer doesnt care about Content Security Policy (CSP) this week and how can I make some sysadmins life worse with my report?

sub

A preface needs to be made prior to this post kicking off; this is specifically for PENTESTING, not bug bounty. This is not a walkthrough on how to sneak in edge case XSS bounties. The finding here is not a traditional XSS; it is a lack of or misconfigured CSP.

We all know what CSP is; a browser mechanism to mitigate XSS and some other attacks (inlcuding clickjacking).

Now it could be argued by very stupid people that if you are essentially MITMing yourself and are in control of the response, why not just strip the CSP out (and other headers) before forwarding to the browser, there fore rendering this entire palava moot? The reason is quite simply, security, like ogres, must have layers. The CSP is not your first layer of XSS defence; that belongs to the secure app development. Its just another thing to help INCASE an XSS vector is identified within the app. Why wouldnt you want your secondar/tertiary etc layers to be as strong as they possibly can be?

But, if you are curious, yes that (stripping the headers from your captured response) would work since you are in control of the response before it hits the browser, doing that will allow the arbritrary JS to run. That is in no way helpful or useful, nor reflective of what is actually occuring. We are simply using the self XSS as a vehicle to demonstrate how if there was a traditional XSS within the application, a misconfigured CSP will let it slip by.

Lets run through some scenarios. You come across a webapp. It is returning no CSP in its responses. What does this mean and what can we do with this?

nocsp

Let us assume the testing has almost finished. You did not find vulnerabilities within the application; no sinks to work DOM XSS and no interactions from blind XSS. The lack of a CSP header is moot at this point right? It cant be weaponised can it?

Not necessarily. The third category of XSS, self, is still valid here.

For the showcases in this post, we will be using a little python script taken from here and fixed up with a little assistance from the friendly robot overlord. I have provided it in full so you can recreate these demos, because its FRIGHTNINGLY HARD to find a public site that has a really strict CSP on it.

#!/usr/bin/env python3
import http.server

class MyHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
    def end_headers(self):
        #self.send_my_headers()
        super().end_headers()

    def send_my_headers(self):
        self.send_header("Content-Security-Policy",
                         "default-src 'self'; "
                         "script-src 'self'; "
                         "style-src 'self'; "
                         "img-src 'self' data:; "
                         "font-src 'self'; "
                         "connect-src 'self'; "
                         "frame-src 'self'; "
                         "object-src 'none'; "
                         "media-src 'self'; "
                         "manifest-src 'self'; "
                         "worker-src 'self'; "
                         "child-src 'self'; "
                         "form-action 'self'; "
                         "frame-ancestors 'none'; "
                         "require-trusted-types-for 'script';")

if __name__ == '__main__':
    http.server.test(HandlerClass=MyHTTPRequestHandler)

You will notice the sending of the headers is commented out; for our first demonstration we will not be sending a CSP.

Very few people I have interviewed have been able to tell me about the existance of this little action within Burp Intercept.

intercept

This is how we will abuse the lack of CSP; by tampering with the server response before it hits the browser. For this to work, it MUST be a proxied action; we arent popping the dev tools and tampering with an alert on a rendered page, we are manipulating it BEFORE the browser receives the response like so

1

2

Lets look at that last sentence some more. What makes this different to just popping an alert with devtools? The CSP, is what. Its purpose, is as a BROWSER MECHANISM to stop XSS. Hence, manipulating what the browser will receive from the server is what seperates this action from being purely client side.

This is best showcased by what the browser will do with a CSP set to deny inline scripts, IE exactly what was being inserted above to pop an alert. For this, we use the script-src directive. Uncomment the send_my_headers. You will notice you will send more directives than my scrots show; I have purposefully sent just the single one and a CORS for reasons I will not elaborate upon.

self

fail

denied

As mentioned, the inline script that was inserted into the page was blocked. This in turn essentially means if the directive was changed to unsafe-inline, the alert would pop, as you are literally telling the policy “yeah i dont give a FUCK that its unsafe, let me insert scripts into my page”.

It is worth pointing out here that when popping devtools and pushing your alert this way, even with a valid CSP it will still work, because as said we are manipulating the rendered page. A question a colleague had for me caught me offguard and made me question this belief until I tested it in the lab; here is a story in 3 parts. The CSP blocks the alert, then I push it in devtools and we see it pop. I dont know what the third part of the story is. This is an update to this article from the future.

devtools

Lets move beyond contrived. What if we have specified location that is being called? It is called script SOURCE after all. Lets modify the location to point to a local file we are serving.

If you are playing along modify your script-src as appropriate.

script-src http://127.0.0.1:8000/test_host.js;

Capture the req but instead of calling the alert inside the response, we insert a call to the file.

safereq

We can see the alert as we would expect, from our explicitly defined location.

safe

For sanities sake, lets try to call some maliciously hosted JS.

malicious1

maliciousfail

As expected, it fails. The source was explicitly defined as test_host.js.

But what about CSPs that are more open? Well this is where it gets interesting, depending on your scope, the agreed ROE, and timeframe.

Lets change the src to

script-src http://127.0.0.1:8000;

And try calling that malicous JS again

malicious2

evil

This time it pops, because the src wasnt locked down enough.

Hopefully we now understand what the script-src does and what to look for when trying to get a last minute finding.

Lets move onto the other bane of devs existence; the default-src. Those of you familar with CSP will note that I have a lot of unecessary directives in the intial code block. I will not explain what I was testing with that, but know this;

default-src ‘self’; is the same as

Content-Security-Policy: connect-src ‘self’; font-src ‘self’; frame-src ‘self’; img-src ‘self’; manifest-src ‘self’; media-src ‘self’; object-src ‘self’; script-src ‘self’; style-src ‘self’; worker-src ‘self’

Some (aka an absolute SHITLOAD) of directives do not have a fallback and as thus will not do anything unless explictly called out and defined in the CSP.

Heres the interesting part that some devs get confused on; its a FALLBACK, not the DEFAULT STATE, despite the name alluding to this.

Lets have a look at what happens when we define a default src as self, and script src as 127.0.0.1 again.

                         "default-src 'self'; "
                         "script-src http://127.0.0.1:8000; ")

evil2

precedence

Remember, the default is there for particular directives that are not specified; it is the equilevent of the fortigate any/any rule at the bottom of the table, there if nothing else matches, not as a first order rule.

Quite honestly, these are the only two important ones. Everything else is just variants on a theme. The style-src abuse works exactly the same as we saw with the excessively open script-src. I am interested in reading writeups of anyone whose been pwned by malicious CSS though (no, not THAT CSS.)

One thing I did find out through producing this post was the existance of frame-ancestors as a directive which renders the X-Frame-Options header obselete. I had no idea CSP was this good, which makes it doubly interesting that so many devs just do not deploy it.

So the TLDR of all this; what is a good CSP? You could do worse than simply using Content-Security-Policy: default-src 'self'; object-src 'none';

The issues start to creep up when you introduce CDNs, frameworks noone wants to host locally, API endpoints that are rapidly changing, and all manner of other not so simple problems that require the introduction of unsafe directives.

Anyway, it was very helpful to futz around with that python script to rapidly test CSP configurations. As I said I have actually not come across a public site that has a CSP I cannot insert an inline script into, so I have been driving myself mental for months reading Mozilla developer doucments without any grounding source of realism. This shit is exactly why I harp on about labbing every few months.

Read my other posts on how gamification has ruined everything, the C.A.P.S.U.L.E.S. project, or my Neural Network drum loop generator. Oh and I have been made a senior consultant.

wolfcastle