Does a Server to Have to Save Nonces to Ensure They Arent Used Again
Content Security Policy: How to create an Iron-Clad nonce based CSP3 policy with Webpack and Nginx
A Content Security Policy helps forestall XSS (Cross Site Scripting) attacks by limiting the way content is served from different sources and from where.
In this Article, I will provide a step past footstep process on how to implement a CSP3 compliant strict-dynamic CSP policy and properly use information technology using Webpack and Nginx to serve static content. This guide tin be followed with whatever file server y'all use.
Past the end of this guide y'all will accept a dark-green CSP checkmark from the CSP Evaluator.
How bad is it out at that place? Really actually bad
Content Security Policies have been effectually for many years now, yet there is shockingly poor documentation and guides around the internet on how to actually implement it. In fact, to see how poorly the online community understands CSP simply download the "CSP Evaluator" Extension on Google Chrome and browse around.
What will yous notice? Showtime on this very website you'll see that Medium has a script-src policy of 'unsafe-eval' 'unsafe-inline' nigh: https: 'self'. Basically this policy is the same every bit having no policy. It allows whatever resource (as long as information technology's https) to inject strings equally code into this website via eval opening up medium.com to XSS attacks and other attack vectors.
WhiteLists, Whitelists, and more Whitelists
The CSP1 and CSP2 spec provided the concept of whitelists. Meaning every single domain that serves or displays on a page must be whitelisted in the CSP policy. The terminate outcome were incredibly long whitelists that were difficult or incommunicable to manage and continue upwards to date.
They also are non secure. In that location are besides a large number of CSP bypasses and the use of JSONP which is prevalent in youtube and angular libraries that can exist used to inject scripts from a seemingly trusted source. Turns out this isn't so uncommon as expressed in this famous Research paper which motivated the creation of strict-dynamic in CSP3
Strict-Dynamic: a simpler more secure way
Strict-dynamic was proposed past W3C and has since been adopted by every browser except for Safari, Safari will likely include it in its side by side major release which is unfortunately almanac. It gets rid of the script-src whitelist requirement and replaces it with a cryptographic nonce. In fact with strict-dynamic, all whitelist items are ignored, as long equally the browser accepts strict-dynamic, otherwise the whitelist items are used. Instead strict-dynamic will let any script as long as it matches an included cryptographic nonce or hash.
Nonce and the failure of existing Webpack modules
It is vital that the nonce is uniquely generated for each page load. It cannot be guessable. Equally long as the nonce is random, strict-dynamic shuts out XSS attacks. If the nonce is deterministic or static, it is useless and defeats the purpose of the CSP policy. It's why you should 100% avoid using Slack'southward Webpack module for CSP, it does not work and will not protect your website. Avert it. They may update it at some bespeak to create random nonces, only the style it is built currently precludes that possibility as it tin can only create a nonce at build time instead of at runtime.
You may besides run into Google'south attempt at this consequence. They instead automatically hash the source files at build time and provide that hash in the html meta policy. While this is actually a secure methodology, it will fail when y'all notwithstanding need to apply other scripts in your app that aren't served by webpack. Once y'all add a nonce based policy with your web server, information technology will disharmonize with Google'south policy every bit the script they use to inject the hash volition be rejected.
While CSP policies are allowed in the HTML Meta page, information technology is impossible to set upwardly the report-uri there. That means that when a user experiences a failure, there will be no logging organisation and it will neglect silently. For that reason it is mostly recommended to use a Content-Security-Policy header from your web server instead.
WALKTHROUGH
Define the Policy
default-src 'cocky';
script-src 'nonce-{random}' 'strict-dynamic' 'unsafe-inline' https:;
object-src 'none';
base-uri 'cocky';
report-uri https://example.com/csp
This should exist the starting point. Strict-dynamic merely applies to the script-src entry. That means you will withal need to white listing other entries. All non existing entries will fallback to default-src and you volition see an error if they are served from a different domain. Encounter all of the available entries hither . You might too consider adding fallback whitelist entries on the script-src to cover Safari users until the next release (In this case, any domain volition be immune to serve inline content as long every bit they are from an https source on Safari, not very secure, a whitelist would be better). For other browsers, only nonce-{random} and strict-dynamic is necessary, the residual will exist ignored.
Add the nonce placeholder to your html template
Now nosotros need to set a fresh window.nonce on every pageload so it can be applied to all of the webpack generated scripts and bundles. To practise that nosotros create a placeholder with any format, in this case I chose **CSP_NONCE**. Note that the script surrounding the window.nonce too needs the place holder or information technology will be rejected past the strict-dynamic policy
<!DOCTYPE html>
<html>
<head>
<title><%= htmlWebpackPlugin.options.championship %></championship>
<script nonce="**CSP_NONCE**">
window.nonce = "**CSP_NONCE**";
</script>
</head>
<body>
<div id="app"></div>
</body>
</html>
Utilise the CSP policy and populate the nonce placeholder
Adjacent we hop over to Nginx where we create a variable and apply it to the header. I apply a variable because it allowed me to organize the CSP headers past section, it also allows me to easily carve up development CSP and production CSP which have slightly different requirements due to http/s and devtools. In my real life project, it looks something like this:
"'nonce-r@ndom' ${defaultsrc} ${imgsrc} ${connectsrc} ${stylesrc} ..."
Then we demand to actually turn 'nonce-random' into a cryptographically random string. Luckily nginx provides that out of the box with '$request_id" which in the CSP policy would await like
'nonce-$request_id'
server{
set up csp " script-src 'nonce- $request_id ' 'strict-dynamic' 'unsafe-inline' https:; object-src 'none'; base of operations-uri 'self'; default-src 'self'; report-uri https://instance.com/csp "
..
..
location / {
..
add_header Content-Security-Policy "${csp}"
try_files /path/to/index.html =404;
}
What near serving the content? Nginx isn't a file server that supports templating, most of the information out in that location suggests using a tertiary political party node server to handle the template. But actually nginx supports this only fine. It is not a default module and so you will need to make sure you've built nginx using the. " — with-http_sub_module" configuration. If you use the official nginx docker container (or openresty), it is included by default.
At present that we have the sub_module enabled we can add the sub_filter additions
add_header Content-Security-Policy "${csp}"
sub_filter_once off;
sub_filter '**CSP_NONCE**' $request_id;
try_files /path/to/index.html =404;
sub_filter_once off; causes nginx to replace more than one instance of the placeholder. This is vital since we need to both apply it to the script tag besides equally set up information technology to the variable. The other command replaces all instances of **CSP_NONCE** with the same request_id listed in the CSP policy
The __webpack_nonce__ subconscious feature
__webpack_nonce__ is a magic, horribly documented characteristic of Webpack, that actually does a great chore. Except information technology requires a lot of hacking to actually apply it.
It must be placed at the meridian of the entry file specified in your Webpack set up (commonly alphabetize.js). The very first line should look like:
__webpack_nonce__ = window.nonce
This specific recipe magically turns on a feature set in Webpack that applies nonces to all scripts loaded in runtime. It actually works quite well. Placing this variable anywhere else will cause it not to work. Line 1 of index.js!
Success! Just kidding. Now allow'due south modify webpack
Why doesn't it work? Well if you apply a nonce-based CSP policy, the automatically generated script that actually loads the entry file tin can never exist run because… you guessed it.. it's non nonced. Webpack but applies the nonces *after* the entry file was loaded and has no style of applying it to the script that loads the entry file. But don't worry, we have a solution for that.
At this point yous should be serving an ironclad CSP3 policy with Nginx, creating a fresh, random nonce on every folio load that gets practical to your index.html file. If you look at the network page or source lawmaking in the devtools of your browser, you volition notice the index.html folio has replaced the **CSP_NONCE** with the same nonce supplied in the CSP header. Excellent! Y'all also have access to the nonce anywhere in your app via window.nonce. This is non a security business organization because the attack vector would crave the hacker to know the nonce before information technology is served.
So at this point, you might enquire why are you lot looking at a white screen?
As I mentioned before, __webpack_nonce__ was only a partially consummate implementation, probably why Slack and Google attempted to create their ain solutions. Since __webpack_nonce__ tin can only be fix in the entry file and non the index.html file, how can the entry file e'er exist loaded unless the nonce is applied to the outer script? Even more complicated if y'all use bundle splitting and/or chunks. Unfortunately the html-webpack-plugin does not have functionality for this so we are stuck.
Custom Plugin
In your webpack.config.js file we need to create a new custom plugin that takes a claw from the html-webpack-plugin and injects the **CSP_NONCE** placeholder to every script tag. Hither comes the magic
var HtmlWebpackPlugin = require("html-webpack-plugin") grade NoncePlaceholder {
apply(compiler) {
compiler.hooks.thisCompilation.tap("NoncePlaceholder", (compilation) => { HtmlWebpackPlugin.getHooks(compilation).afterTemplateExecution.tapAsync(
"NoncePlaceholder",
(information, cb) => {
const { headTags } = data
headTags.forEach((x) => {
10.attributes.nonce = "**CSP_NONCE**"
})
cb(null, data)
}
)
})
}
} var html = new HtmlWebpackPlugin({
title: "title",
template: "index.ejs",
filename: "index.html",
}) const config = {
...
...
plugins: [html, new NoncePlaceholder()]
}
This NoncePlaceholder custom plugin volition at present inject a nonce="**CSP_NONCE**" to every script in the index.html file assuasive it to be covered by the nginx sub_filter and converted to the allowable nonce
What about third political party scripts?
Since the nonce is nowadays in window.nonce, you can apply that nonce to whatever script in your app. For case, for Google Tag Manager y'all might have
googleTagManager.setAttribute("src","https://www.googletagmanager.com/gtag/js?id=" + id) googleTagManager.setAttribute("nonce", window.nonce)
Yous will need to use the window.nonce as a nonce attribute to any imported script or tracker.
Other directives
As y'all apply nonces to allow tertiary party scripts to run, you volition detect a large number of CSP errors. For case Google Analytics requires whitelist entries in img-src and connect-src. These are not covered by strict-dynamic in CSP3. Until the next typhoon comes out from W3C, we still need to white list all other directives. For google you can follow their guide on what needs to exist white listed. For well-nigh, they lack documentation entirely, and you will just demand to test the functionality to see what needs to be whitelisted. Which is one of the reasons why a study-uri is so important.
But don't whitelist everything you see an mistake for! Not everything is necessary for functionality and especially services from Google, or Meta volition constantly endeavour to invade your website with trackers for their own purpose. Your CSP headers will protect your users from that, only whitelist the minimum domains you lot can to achieve the functionality you lot want.
Concluding Thoughts
Why was this so complicated? Why tin't __webpack_nonce__ be ameliorate documented? Why can't it be practical in the alphabetize.html file so that the entry file can exist loaded instead of failing on itself? Why do well-nigh websites have inadequate CSP policies? Why do the webpack plugins created by industry leaders lead people to create insecure policies? Why doesn't html-webpack-plugin allow u.s.a. to gear up a nonce aspect? There are a lot of open source opportunities here. But subsequently spending over a week on something that should have taken a few hours, I hope this web log post can help become people on the correct track and implement a solid CSP3 policy
Source: https://towardsdatascience.com/content-security-policy-how-to-create-an-iron-clad-nonce-based-csp3-policy-with-webpack-and-nginx-ce5a4605db90?source=rss----7f60cf5620c9---4
Enregistrer un commentaire for "Does a Server to Have to Save Nonces to Ensure They Arent Used Again"