Bypassing paths in CSP with open redirects + mitigation

In CSP > 1 you can use paths instead of just origin (scheme, port & domain) to specify a whitelisted resource. For example, script-src example.com/foo/bar/ can be used to whitelist a script on the page example.com/foo/bar/script.js.

In the above example, the browser should not load a script from example.com/foo/script.js - however, this can be bypassed if you have an open redirect on another or the the current domain. This is an intended behavior because of another, much worse issue with CSP.

Using paths in CSP can reduce the attack surface greatly. It's highly recommended to use paths in your CSP, however, there's not many actors that does this. Stripe is one example of where they have a huge policy and therefore want to reduce the attack surface by specifying paths. Also Google recommends specifying paths in your CSP.

I would like to shine some light on the bypass and why it's there. Because it's a wontfix both attackers and defenders should know about this. There's also some mitigation against this behavior that I will explain at the end of this article.


The bypass

So the bypass is easy to understand. If we take script-src 'self' google.com/recaptcha/ as an example, the attacker just need to find an open redirect on the current domain to be able to fetch scripts from google.com/*. The attacker use the open redirect to point to any location on a whitelisted domain in the CSP.

It will also work if we have script-src example.com/scripts/ google.com/recaptcha/ and if any page on example.com/scripts/* have an open redirect, the attacker could load script from google.com/*. The attacker can now use
<script src="//example.com/scripts/file.php?go=//google.com/evil/script.js"></script> without it being blocked by the CSP, and the file script.js will be executed.

Here's a PoC

The problem

So why does CSP work this way? The main reason is that we do not want to leak the paths cross-origin. In Egor Homakov’s Using Content-Security-Policy for Evil this is explained further.

Let's say you're logged in on example.com and every time you visit that site (while logged in), you are redirected to example.com/profile/1738 where "1738" is your member ID. An attacker can now create (on any site!) something like img-src example.com example.com/profile/1738 with the HTML
<img src="//example.com"/> and if this loads (not blocked by CSP), you have ID 1738. The img - tag would follow the redirect to example.com/profile/1738 which is whitelisted in the CSP, and therefore no error.

There're better ways to design this, but this was just a simple explanation. You can also read Deanonymizing Facebook Users By CSP Bruteforcing which also discuss this method further.

Some mitigation

This has been fixed in CSP because otherwise you could've used CSP to fingerprint users fairly easy. The fix is that the browser should ignore paths (but not origins!) on redirects. However, this introduced the path bypass. Now you can't use the above method to fingerprint users because paths will not be sent cross-origin. Read more in the specification § 4.2.2.3. Paths and Redirects.

If you are blue team and uses paths (good on you), don't rely on them! The first thing you should do is tighten the paths even more, because the attacker needs an open redirect in a whitelisted domain + path. If you're whitelisting only one script on an external site, you can specify the complete URL in your CSP to reduce the attack surface as much as possible.

The directive that is in most danger here is of course script-src. There's a few small things you can do to make it harder for the attacker to exploit this vulnerability. One thing is by using require-sri-for script which may help if the attacker only can control the src - attribute. If the attacker can't set the integrity - attribute, the resource will not be loaded.

Sometimes 'self' is not the best option because you can point to any path in the current origin. You can use data: or unsafe-inline in combination with nonce or hash to avoid loading script as resources. However, this is very dangerous and should be only be used if you know exactly what you're doing. You can also use your exact domain with paths,
script-src example.com/static/script.js instead of 'self' - this will force the attacker to find an open redirect in /static/ to be present, and then use that to bypass paths specified in another whitelisted domain - (PoC) - this method can be hard to implement, but is worth it if you're relying on paths.

Another thing is sanitizing (if you can) user inputs. For instance, if a client can show images from a specific domain, you should check so the paths corresponds with the whitelisted paths in your CSP.


Summary and final words

Due to an intended behaviour in CSP>1, it's possible to bypass whitelisted paths with a redirect. This can both be a security,- and privacy-issue.

If you're using paths in your CSP, make sure that the paths are as strict as they can be. Don't rely on paths and if you're good with handling redirects, you can avoid 'self' and point directly to the file to reduce the possibility that an attacker find an open redirect on 'self'.

Special thanks to @fransrosen for the first PoC. He and I had a long discussion about this matter and got me inspired to write about this. He also shared interesting statistic on how many domains that actually uses paths in script-src and it seems to be alarmingly few domains that do this (perhaps of different reasons).

/ @dotchloe