Open lweichselbaum opened 3 years ago
It would be great if an nonce could be passed to the page at runtime somehow.
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.
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
@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.
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!
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:
nonce reuse: if nonces are not changing on every page reload (even cached responses are a problem!) they'll cause the entire CSP to become bypassable (this is similar to XSRF tokens). Nonces changing often (even if every 10 minutes) are still easy to bypass as the attacker could fetch the current nonce in less than a second.
host allowlists: host entries in the CSP often lead to bypasses (JSONP, hosting of AngularJS, ..), esp. if there are CDNs in the allowlist (which is often necessary). More about how easy it is to bypass allowlists in CSP (even automatically) can be found here.
'strict-dynamic'
by default, which would cause the entire allowlist to be ignored and therefore would remove all potential allowlist based bypasses.However, when using hashes alone (which likely is the recommendation of SPAs if they can't generated nonces on every page load) it's not possible to hash sourced scripts (actually it is, but only in Chrome right now..). Since alllowlists shouldn't be used either the only way to get a hash-only policy working is to rewrite sourced scripts that can't be inlined (for example if they're hosted somewhere else) into a hashable inline script block that dynamically loads these scripts. E.g. these two sourced scripts need to be converted into a hashable inline script.:
<script src="https://example.org/foo.js"></script>
<script src="https://example.org/bar.js"></script>
<!-- 'sha256-XuHWTNmJH6UK7Qi9Te9PhtU5CRMuihd33ChamIrBDsI=' -->
<script>
var scripts = [ 'https://example.org/foo.js', 'https://example.org/bar.js'];
scripts.forEach(function(scriptUrl) {
var s = document.createElement('script');
s.src = scriptUrl;
s.async = false; // to preserve execution order
document.head.appendChild(s);
});
</script>
Regarding @Slapbox's question on nonces vs. hashes:
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.
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.
@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.
@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.
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
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.
Nonce reuse
I have a question in regard to
nonceEnabled
: I assume that thecsp-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[ ]
)