Automatic XSS Prevention

2 examples 0/3 challenges solved


Description

Learn how to automatically stop XSS attacks with Content Security Policy and Subresource Integrity.

Examples

Ready for the full course?

What's included?


Learn XSS attacks, exploits, fixes and data breach prevention.

Lifetime access of:

  • Expert-led training
  • Interactive examples
  • Live code challenges
  • Advanced attacks
  • Full exploit development
  • Fixes and prevention
  • Engineering best practices
YES! I'M READY TO MASTER XSS > One time payment for lifelong skills! Just $99/user* * WAY less than the price of an XSS bounty

video transcript

In this recipe, you're gonna learn about CSP and SRI for XSS when SHTF.

*smh

Hello, world! I'm Jesse from Chef Secure, and Content Security Policy, or CSP, is a way to protect your website from XSS attacks even when you have a vulnerability.

And SRI is a way to stop third-party scripts from causing damage even when the host gets taken over by an attacker and they change the script.

Both of these are essential to security in your application in depth, because when something goes wrong or fails, and, trust me, that will happen, your application is not left completely vulnerable and open to attackers.

Content Security Policy works by whitelisting the resources allowed to load on your webpage, like scripts and styles, and anything not on that list will be blocked.

CSP is set in a response header set by your web server named Content-Security-Policy or in an HTML meta tag with an http-equiv attribute set to the same value.

Your policy whitelist becomes the header value or the content attribute value in the meta tag.

Content-Security-Policy: policy
<meta http-equiv="Content-Security-Policy" content="policy">

This policy defines the valid resources for your webpage, with directives, which are mostly rules for elements and their allowed sources.

Now, normally you start with the default-src directive, which is just a fallback in case any of the other directives go...
🛸
missing.

So if you've got some trust issues, you can block all resources by specifying 'none'.

This is, by far, the most secure option. But if you at least trust yourself, and any uploads you may have hosted on your site, you can specify 'self' instead.

Next, the script-src directive is likely the most important part of your CSP when defending against XSS attacks, because it tells your browser exactly what scripts are allowed to run.

And if you can block an attacker's script from executing, there's a lot less damage they can do.

Since default-src is just a fallback for missing directives, your script src needs to list all resources used, even if it's already included in the default-src.

Most websites need to allow scripts from their own host by using 'self' and to include third-party scripts, like CDNs, list the full hostname with our without the scheme.

So you can have

htts://cdn.example.com
cdn.example.com
*.example.com
(matches any domain.example.com)
*
(matches EVERYTHING)

But be extra careful with wildcards because they can match everything.

Locking down your script-src directive will prevent attackers from pulling scripts from their own servers to deliver something like a cryptominer or a ransomware download

But what about injecting scripts directly into the page?

Thankfully, CSP is secure by default and will prevent XSS attacks like this.

But... it's probably gonna break your webpages too.

While it's almost always a bad idea to disable security features, I'll show you how to do it temporarily

TEMPORARILY!

so you can get rolling with your own CSP, then I'll show you the right way to fix your site so you can lock it down properly.

Adding 'unsafe-eval' as a source allows you to run potentially dangerous JavaScript functions that evaluate strings as code.

Again, these functions are

eval('{{ escapeJS(data) }}')
new Function('{{ escapeJS(data) }}')
setTimeout('{{ escapeJS(data) }}',0)
setInterval('{{ escapeJS(data) }}',0)
setImmediate('{{ escapeJS(data) }}',0)

Note that the last three functions aren't blocked without 'unsafe-eval' if they're called safely using a function argument, rather than a string argument.

setTimeout(myFn,0)
setInterval(function(){...},0)
setImmediate(()=>{...},0)

Some of your dependencies, like older JavaScript frameworks, may need 'unsafe-eval' to run properly. And if that's the case, be sure and go through your own code, and look for any unsafe uses of the eval-family of functions.

Then you can work on upgrading your framework or dependency, so you can remove 'unsafe-eval'.

The next insecure source option is 'unsafe-inline' which removes most of CSP's XSS defenses.

By default, CSP blocks inline scripts from executing – like inline event handlers and script tags containing code – and, more than likely, you have both in your code somewhere, so you'll probably want to add 'unsafe-inline' as a source –

Let's... fix this problem so you don't have to do that.

Fix your inline event handlers like this:

Move untrusted or dynamic data into a new data attribute.

Then make a new JavaScript file, and put your event handler code inside a new function.

And update your code to use the data attribute.

Next, add the event listener.

And, finally, load the JavaScript file in your page.

Let's get some hands-on experience with this.

Go to the Refactoring Inline Event Handlers example for this recipe below.

For this example, you're gonna copy the HTML into a new file on your computer.

Create a new file called refactor.html. Paste in the code. And look at the end of the inline event handler and switch out my name for your own.

Then save the file, and open it in your browser.

You see that once you touch the cookie, sadly, it escapes out of your hands and an alert pops up asking you to kindly stop.

Go back to the code now.

Now, within the head of the document, add a meta tag for the CSP with

<meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline'">

Refresh the page, and you'll see that it's still working.

But we want to lock down the script-src, so go back to the CSP and each directive is separated with a semicolon, so add that.

<meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline'; script-src 'self'">

Refresh the page again, and you'll see that the cookie no longer runs away.

This is because the inline event handler is blocked by the CSP, and you can even verify this with the error message in the console.

Let's fix this now. Remember, we're considering your name as dynamic data, so the first step is to move it into a new data attribute.

<span id="cookie" ... data-name="YOUR_NAME"

And this, of course, would need to be HTML escaped in your framework.

The next step is to make a new JavaScript file. I'll name it cookiesec.js Then move the event handler into a new function.

So define the function with function then the function name. I'll call it hoverHandler. Then paste the inline event handler inside. I'm gonna fix up the spacing here really quick.

function hoverHandler() {
this.style.top=`${innerHeight-100)*Math.random()}px`;
this.style.left=`${innerWidth-100)*Math.random()}px`;
alert('YOUR_NAME! Please stop!');
}

And the next step is to use the data attribute. So change the alert string to a template literal And replace your name with the data attribute using

alert(`${this.dataset.name}! Please stop!`);

After this, you just need to add the event handler with

document.getElementById('cookie').addEventListener('mouseover', hoverHandler)

Finally, just add the script to the page with

<script src="cookiesec.js"></script>

Save it. Refresh the page, and everything is now working again with a locked down script-src. And there are no more errors in the console.

By fixing inline event handlers like this you get three bonuses:

  1. you reduce complexity by only needing HTML escaping instead of HTML and JavaScript
  2. you properly separate your HTML and JavaScript code for better maintainability
  3. and, most importantly, you get to keep the XSS protections that CSP provides

Similar to this, JavaScript URLs are also blocked without 'unsafe-inline' so they just need to be replaced with event handlers.

As I said earlier, script tags containing code are also considered unsafe inline scripts.

To fix these, move untrusted or dynamic data to a data attribute.

Then move your code inside a JavaScript file.

Update it to use the data attribute.

Then load the file from the script tag.

Let's do this in the Refactoring Inline Scripts example below, and, as I promised in the escaping recipe, I'll show you how to properly handle working with JSON objects.

Again, for this example, you're gonna copy the HTML into a new file. Then I'll create refactor2.html Paste the code inside. And switch out my name in the JSON object with your own.

Now let's suppose this JSON object right here is dynamically added to your script in your framework with proper JSON encoding.

Save the file and open it in your browser. And you see you get a nice, friendly greeting.

Now we're gonna add the CSP, so open the file. And add the meta tag. And we'll just add the script-src this time with

<meta http-equiv="Content-Security-Policy" content="script-src 'self'">

Refresh the page now, and you'll see that you've got an unthoughtful, generic greeting that doesn't make you feel good.

Open your console again and you'll see that the CSP blocked the inline script.

So we've got to fix this now. The first step is to move the JSON object to a new data attribute.

<span id="name" ... data-user="{"name":"YOUR_NAME"}"

Now comes escaping. There isn't any JSON escaping to do, so next we need to HTML escape the double quotes by turning them into

<span id="name" ... data-user="{&quot;name&quot;:&quot;YOUR_NAME&quot;}"

Do it for all of them. By the way, in your framework, you'll, of course, use your own escaping functions instead of doing it manually like this.

Now that the JSON data's been relocated, create a new JavaScript file in the same directory. I'll call it friendly.js. And let's set a variable to the span element with

let name = document.getElementById('name')

Then update it's value with

name.textContent =

And now we need to parse the JSON to turn it from a regular 'ol string back to a real JavaScript object

name.textContent = JSON.parse(name.dataset.user)

Then, finally, from that user object we need to get the name, so just type in

name.textContent = JSON.parse(name.dataset.user).name

Then remove the contents of the script tag in the HTML file. And set it's source equal to your file.

<script src="friendly.js"></script>

Save this. Refresh the page now, and you'll see everything is fix with a personalized greeting and you've locked down your CSP to prevent unsafe inline scripts from executing.

For extra practice, you can pause the video right now and try the other recommendation I gave you in the escaping recipe, which is to use a script tag with the type set to application/json instead of using a data attribute.

Depending on how many inline scripts you have, this may take a bit of time.

So, while you work on that, CSP gives you two ways to allow inline script tags without having to break security with 'unsafe-inline'.

The first is to use a hash.

With a sha256, 384 or 512 hash of your entire inline script code, you can whitelist inline scripts.

Add this as a source in single quotes, by adding the sha-algorithm used, followed by a dash, then the base64 encoded hash value.

'sha256-base64(sha256(...))'
'sha384-base64(sha384(...))'
'sha512-base64(sha512(...))'

The second way is to use a nonce, which is short for a number used once.

(kind of: https://en.wiktionary.org/wiki/nonce)

It's basically a random token generated with every response.

Every inline script tag will need to set a nonce attribute to this value,

<script nonce=RANDOM_TOKEN></script>

and the CSP source will have, in single quotes, nonce, -, then the nonce value.

'nonce-RANDOM_TOKEN'

Look for a library or module that does this for you, instead of building it yourself, so you can save time and make sure it's done right.

Moving on from scripts, another directive that can use 'unsafe-inline' is style-src.

If you recall, attackers can use styles to make exploiting a page easier, so, by default, CSP blocks inline CSS styles that use the style attribute or style tags.

Fixing blocked styles works like this:

Move the CSS from style tags into a new file.

Afterward, move any style attributes into a new CSS class within the same file.

Add this class to the element's class attribute.

Then, finally, link this file in the HEAD of the document with a link tag containing a rel attribute set to stylesheet and an href attribute set to your file.

And, again, hashes and nonces can be used for inline style tags too.

Locking down your default-src, scripts-src and style-src will do a lot in limiting XSS attacks on your site, but there are still several other options available for extra security.

I'm just gonna vomit them all out to you right now, starting with the ones that fall back to default-src.

And if you need to dive deeper into any of them, I'll provide you with a link to Mozilla's excellent CSP documentation for reference below.

https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP

Here we go!

connect-src tells your browser what sources scripts can connect to in the background.

This effects things like AJAX, fetch(), beacons, websockets and <a ping="URL">

font-src specifies what font resources can be loaded on your page.

frame-src is for embedded iframes.

img-src is for images.

manifest-src is for the manifest.json file used in progressive web applications

media-src is for <audio>, <video> and <track>

object-src is for <object>, <embed> and <applet> and their nested contexts.

worker-src is for Workers, Shared Workers and Service Workers

If your CSP doesn't have any of those directives listed, your default-src will be used instead.

Now let's look at what's not covered by default-src.

The next two directives are for securing connections, only one should be set at a time, and they don't have any source list because they're just flags.

The first is block-all-mixed-content which prevents loading resources over http when the page is using https.

The second is upgrade-insecure-requests and will automatically upgrade http requests for resources to https.

Here are the last of the remaining directives:

base-url tells your browser what sources are allowed to be used for the base element. Doesn't sound too exciting, but later we'll go over how to exploit this if it's missing from a CSP.

The frame-ancestors directive handles the embedding of the page within an by other webpages. And since this doesn't fall back to default-src, missing this directive will allow any site to secretly, or not so secretly, embed yours unless you have other protections set, like the X-Frame-Options header.

And, finally, the sandbox directive works in the same way as an iframe sandbox attribute which essentially restricts several actions on the page. I won't go into this, but if you think you'll need them, feel free to look further into this, and, if you do, know that you'll need to use a header for your CSP and it won't work in the meta tag.

This should be more than enough to secure your application with CSP but if you find you need a little more fine-tuning, check out the CSP documentation link below.

The last defense we'll go over in this recipe is Subresource Integrity (SRI).

Remember hashing inline scripts for CSP?

It's essentially the same thing.

Scripts hosted on other sites, especially scripts you just randomly found on GitHub or someone's blog need to be verified that they do what you think they do and nothing else behind the scenes.

Once you trust what you're putting into your website and delivering to every one of your users SRI will help make sure it doesn't change unexpectedly.

The shasum or openssl commands can help you generate this hash, and then you'll need to encode it in base64.

But Mozilla, again, came to the rescue and made it easier by creating an online service for this.

I like easy!

Open the link below, or go to srihash.org.

https://www.srihash.org

You see all you have to do is enter a URL for the resource you want to use and it'll generate a hash for you.

So we're gonna use BootstrapCDN as an example. So go to bootstrapcdn.com as well.

https://www.bootstrap.com

Select the Complete Javascript link. It's copied to the clipboard, now go back to the SRI hash generator.

Paste in the link, and click on hash.

You see that it generates a script with the integrity attribute and its value set to the algorithm, a dash, then the encoded hash.

In addition, a crossorigin attribute is set to anonymous, which prevents your browser from sending any credentials to BootstrapCDN while SRI performs its background fetch request to verify the integrity of the hash.

Now there are two cases where you can't use SRI for scripts.

The first is fixable: your CDN doesn't have CORS enabled, so SRI can't verify the integrity from your website.

To fix this, you simply need to add the CORS headers in your CDN so the script integrity can be verified.

Access-Control-Allow-Origin: https://example.com

The second situation is, unfortunately, not fixable.

If you're referencing a JavaScript resource that's constantly updated with new features and fixes, any new change will break the integrity of the hash.

Your script will probably look something like this.

<script src="https://js.example.com/v3"></script>

From here, you have two options: you can either use SRI and wait for your site to break with every update. or, just leave it off.

Always remember that user experience in security is very important. Security should enhance applications and not just break them, but, ultimately, it's up to you and your team to decide what risks you're willing to take.

However!

This doesn't mean you can use this as an excuse when you just want to be lazy and procrastinate because there's a lot of upfront work involved.

(sigh)

Just so you know.

*JSYK