Skip to main content

Content Security Policies and Content Delivery Networks

• Tagged Security and Performance

Report URI is a service which provides monitoring of things like Content Security Policy violations on your website. They had a pentest carried out and this section about their CSP stuck out to me:

You can also see that our external list of sites that can load script is very small, by design, and does not include any of the variety of JavaScript CDNs out there. Inclusion of something like cdnjs.com or code.jquery.com would have allowed the inclusion of outdated libraries from those locations that have potential security issues, allowing an attacker to cause harm. This is the reason we self-host our own JavaScript files but you should also consider specifying a path in your CSP directives if that isn’t an option like cdnjs.com/jquery/not-vulnerable-version. [Source]

I’d never thought of this before. If your site has an XSS flaw and you’ve got a third-party CDN in your CSP, a potential attacker could inject their own code into your site and load in outdated versions of libraries via the CDN to introduce even more vulnerabilities!

What’s particuarly interesting is the suggestion to specify paths in the CSP, which I didn’t know was possible. MDN’s Content Security Policy examples don’t show that being used, only domain names (like script-src awmb.uk) and subdomain wildcards (like script-src *.awmb.uk). It is, however, mentioned on the CSP header reference page.

We can also do a simple test ourselves. Here’s a page which loads in multiple versions of jQuery, because who cares about speed and performance?

<!DOCTYPE html>
<html>
<head>
    <title>jQueryLand</title>
</head>
<body>
    <h1>Welcome to jQueryLand</h1>
    <p>Where you get 3 jQueries for the price of one!</p>
    <!-- Latest -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
    <!-- Very old -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.10.0/jquery.min.js"></script>
    <!-- Old and from another CDN -->
    <script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
</body>
</html>

First up, loading the page with no CSP loads all 3 libraries, as expected:

A screenshot of the rendered webpage, showing the dev tools Network tab and 3 libraries loaded.

Next, let’s add the CSP default-src 'none'; script-src cdnjs.cloudflare.com, which should get us down to two versions.

A screenshot of the rendered webpage, showing the dev tools Network tab and 2 libraries loaded. A screenshot of the rendered webpage, showing the dev tools Console tab reporting it reused to load a script from code.jquery.com.

All good so far, jQueryLand is gradually losing its appeal and is down two 2 copies of the library. I believe the repeated errors are due to CORS, but I’m not 100% sure and perhaps that’s an adventure for another post. Finally, let’s try a CSP with a path: default-src 'none'; script-src cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/.

A screenshot of the rendered webpage, showing the dev tools Network tab and 1 library loaded. A screenshot of the rendered webpage, showing the dev tools Console tab reporting it reused to load scripts from code.jquery.com and cdnjs.cloudflare.com.

Now we’re loading in jQuery from a speedy CDN and have restricted it to a version we know and trust1. Nifty! (Although, jQueryLand really isn’t living up to the name any more.)

Of course, the (arguably) easier option here would be to just self-host your own files, where you can also have the benefits of automatically removing unused code as part of your build process (which is what I do on this site). There are definitely situations though where a big CDN like this makes sense – maybe you can’t manage your own CDN for some reason, or you’re developing something where it’d make the build system too cumbersome, or perhaps you’re CodePen and need a stock of libraries to offer up to your users.

But hey, today I learned you can put paths in CSPs, and I’m sure that’ll come in useful one day.

  1. We mostly know and trust it. It’d be a good idea to use the integrity attribute in the <script> tag to ensure the script at that path is actually the script we want. cdnjs will provide you with the hash you need if you copy the <script> tag using their magic button. I omitted it here to make the code exerpt prettier. ⤶ Back


Next post: videocall.guide

Previous post: A Blog Is Born