slackhq / csp-html-webpack-plugin

A plugin which, when combined with HTMLWebpackPlugin, adds CSP tags to the HTML output.
MIT License
164 stars 40 forks source link

[Security] Nonce reuse #82

Open lweichselbaum opened 3 years ago

lweichselbaum commented 3 years ago

Nonce reuse

I have a question in regard to nonceEnabled: I assume that the csp-html-webpack-plugin is only invoked at build time and not for every http request. If this assumption is correct, how can one prevent attackers from just copying CSP nonces and by that bypassing the entire CSP?

Relevant section in the CSP spec is here: https://w3c.github.io/webappsec-csp/#security-nonces

What type of issue is this? (place an x in one of the [ ])

Requirements (place an x in each of the [ ])


Nantris commented 3 years ago

It would be great if an nonce could be passed to the page at runtime somehow.

Nantris commented 3 years ago

I came across this option but I'm not sure the CSP HTML Webpack Plugin makes use of it?

https://webpack.js.org/guides/csp/

I've tried setting __webpack_nonce__ but it doesn't seem to end up applied.

AnujRNair commented 3 years ago

You're correct in your assumption - nonces are only useful if they are changing often, ideally on every page load. If your app will be deployed for long periods of time without its source code changing, you should either:

1) Use shas instead, which checks the contents of a script block matches in a more strict fashion 2) Generate your own nonces server side and put them into your html source on each page load, using the __webpack_nonce__ feature as suggested above. Here's a stackoverflow post with some more details

At Slack, we have opted to only use shas

Nantris commented 3 years ago

@AnujRNair thanks for your reply! Are there any potential risks that nonce can address which hashes cannot?

We tried getting __webpack_nonce__ working without luck, so if hashes alone eliminate any need for an nonce, that would be great news.

AnujRNair commented 3 years ago

Not to my knowledge. As far as I am aware, they're as good as each other. Someone can correct me if you find otherwise!

lweichselbaum commented 3 years ago

Thanks for confirming @AnujRNair! I think that CSP can serve as strong defence-in-depth mechanism, if configured properly. Unfortunately, it's very easy to misconfigure a CSP and by that making it trivially bypassable which will not only take away any security benefits, but will also give a false sense of security.

I really like this module, but I think the way (esp. the defaults) are implemented right now could cause many applications to end up with a trivially bypassable CSP. I think it's crucial to provide a secure-by-default configuration as most users won't know all the intricate details about CSP:

Regarding @Slapbox's question on nonces vs. hashes:

Nantris commented 3 years ago

Great writeup @lweichselbaum.

I think it's crucial to provide a secure-by-default configuration as most users won't know all the intricate details about CSP

I think this part is a bit tough though. Any secure CSP will break most projects and will only be fixable by users who understand CSP in the first place. Providing a secure example isn't enough and so I feel like directing people to external resources might be best here.

lweichselbaum commented 3 years ago

Thank you!

Any secure CSP will break most projects and will only be fixable by users who understand CSP in the first place.

I think it's possible to have a secure CSP without breaking most projects. The rewriting of sourced (external) scripts (which were previously allowed by host allowlists or nonces) into a hashable inline script as shown in the code snippet above can be automated. With that a CSP based only on hashes (without allowlists) can be used as a secure default. Of course, if you can use nonces that change on every page load (nonce actually stands for number used once), it would be easier to deploy a strong CSP as you wouldn't have to rewrite script tags.

Nantris commented 3 years ago

@AnujRNair I found that hashes aren't applied except to inlined scripts and styles - so I'm wondering if you have any advice for getting __webpack_nonce__ working, since otherwise we're back to the nonce reuse issue.

Nantris commented 3 years ago

@AnujRNair how are you going about using SHAs at Slack? It seems like this is the route we'll have to go, but it's not really supported yet in the plugin. Any advice for how to move forward? It seems like we've just hit an unbreakable wall here.

AnujRNair commented 3 years ago

Sorry for the delay in getting back to you here

@lweichselbaum thanks so much for the detailed writeup and presentation - it was extremely useful. I would love to create a secure-out-of-the-box plugin here - it looks like we probably can't use nonces since webpack generates everything statically at compile time, so we might have to switch to using hashes only and deprecate nonces to ensure we don't provide a false sense of security. Unless you know of a way that we might be able to use nonces with webpack here?

@Slapbox that is correct - only inline styles and scripts are hashed at the moment. https://github.com/slackhq/csp-html-webpack-plugin/pull/87 is exploring whether we can hash external script and style tags as well

At Slack, we only use hashes, and we generate our script tags as @lweichselbaum recommended. We do this by defining a custom template, and passing it to the HtmlWebpackPlugin instance.

You can do the same with something similar:

webpack config:

function generateTemplateParams(compilation, assets) {
  return {
    assetsJs: assets.js,
    assetsCss: assets.css,
  }
}

new HtmlWebpackPlugin({
  templateParameters: generateTemplateParams,
  template: 'path/to/template.ejs',
  inject: false, // don't inject script and style tags - we'll do so manually

template.ejs:

<% _.each(assetsJs, function(js, idx) { %>
    <script>
        const s<%= idx %> = document.createElement('script');
        s<%= idx %>.src = '<%= js %>';
        document.getElementsByTagName('head')[0].appendChild(s<%= idx %>);
    </script>
<% }) %>

This is the basics, but there's lots more customization you can do there

eamodio commented 3 years ago

Maybe there could be a way to have the nonce injection be a replaceable token, so that the nonce token could be replaced dynamically at runtime? I would like that ability, because I am attempting to do just that, until a hash can be generated for an external script/stylesheet.

I've got this working currently, by overriding the createNonce method and replacing it with a token.