Jss: Support CSP by adding __webpack_nonce__ to the style tag

Created on 7 Aug 2017  Â·  33Comments  Â·  Source: cssinjs/jss

This is really just a conceptual question to make sure that I understand the philosophy and core technology of the project.

From my cursory overview of the project, it appears to work by inserting and updating style elements into the DOM. This seems to me to be incompatible at a base level with Content Security Policy security headers, specifically the style-src header.

For example, In order to get the react integration to work, the tightest security policy I am able to set is default-src 'self'; style-src 'unsafe-inline' blob:. For a CSP to provide realistic XSS protection, the 'unsafe-inline' keyword cannot be included. Is this conflict an implementation detail that could be potentially mitigated, or am I correct in assessing that it's a root conceptual incompatibility?

moderate enhancement

All 33 comments

I haven't researched this topic. All we do is we create a style tag and insert css rules by using style.textContent or style.sheet.insertRule api.

I would be happy to add more information about this to the documentation, are you interested to contribute?

So we need to support __webpack_nonce__.

Ah yes that tweet thread is very informative. The dynamic nonce creation definitely seems the way to go and it's great that there's work being done in webpack.

I would be happy if someone takes care of this for jss.

I'm happy to contribute, though it may take quite some time. Seems as though I'm the only one searching for this feature in this project at the moment though so I'm happy to slowly chip away at it.

My time investment will unfortunately just be very limited.

We recently added support for this in styled-components, if you go through the PR history you should find the PR somewhere. 😊

Nice! Looks like this line is the special sauce? Just need to reference the __webpack_nonce__ global?

https://github.com/styled-components/styled-components/pull/1022/files#diff-fc2c32c33f67b7c15121015920ab3609R74

Looks like it, should be fairly easy to add.

We need to add documentation.

I ran into this today. If it can be of help for anyone else, here are the steps I did.

First, a word on _nonce_:

MDN notes the following:

'nonce-{base64-value}'
A whitelist for specific inline scripts using a cryptographic nonce (number used once). The server must generate a unique nonce value each time it transmits a policy. It is critical to provide an unguessable nonce, as bypassing a resource’s policy is otherwise trivial. See unsafe inline script for an example.

I found it ~very hard~ impossible to generate a unique nonce for every page visit, as webpack must know the nonce at bundle time (unless I'm missing something), while the server must know the nonce at runtime for including it in the CSP rules. If anyone have any suggestions, please let me know, but for now, I'm using a hard-coded Base64-encoded guid value.

Anyhow, here are the steps.

  1. In my server startup, I set the nonce value

Generate a Base64 guid:

  1. Get a guid: https://www.guidgenerator.com/online-guid-generator.aspx
  2. Encode the guid: https://www.base64encode.org/
// server.js
const WEBPACK_NONCE = 'N2M0MDhkN2EtMmRkYi00MTExLWFhM2YtNDhkNTc4NGJhMjA3'
  1. Also during server startup, I set the style-src CSP rule to contain the nonce, as described here
// server.js
import helmet from 'helmet';
app.use(
  helmet.contentSecurityPolicy({
    directives: {
      /* ... */
      styleSrc: [
        "'self'",
        `'nonce-${WEBPACK_NONCE}'`,
      ],
    },
  })
);

I'm using Helmet for misc security concerns. They also have a good tutorial on CSP

When loading the initial index page, the Content-Security-Policy header should contain

style-src 'self' 'nonce-N2M0MDhkN2EtMmRkYi00MTExLWFhM2YtNDhkNTc4NGJhMjA3'
  1. In webpack config:

Set the magical global.__webpack_nonce__ variable to the same nonce value

// webpack.config.js
const WEBPACK_NONCE = 'N2M0MDhkN2EtMmRkYi00MTExLWFhM2YtNDhkNTc4NGJhMjA3'
module.exports = {
  /* ... */
  plugins: {
    new webpack.DefinePlugin({
      'global.__webpack_nonce__': JSON.stringify(WEBPACK_NONCE),
    }),
  },
}

At this point, the style blocks generated by jss should render with the nonce value (<style nonce>) when you inspect the page.

stylenonce

I guess chrome doesn't show the nonce value for security reasons.

However, you might still have some CSP violations if you use style/css/sass in addition to jss. To fix this, set Webpack's style-loader to also set the nonce:

{
  test: /\.css$/,
  use: [
    {
      loader: 'style-loader',
      options: {
        attrs: {
          nonce: WEBPACK_NONCE,
        },
      },
    },
    'css-loader',
  ],
},

{
  test: /\.scss$/,
  use: [
    {
      loader: 'style-loader',
      options: {
        attrs: {
          nonce: WEBPACK_NONCE,
        },
      },
    },
    {
      loader: 'css-loader',
    },
    {
      loader: 'sass-loader',
    },
  ],
},

This fixed all style-related CSP issues for me, although I'm not too happy about the hard-coded nonce đŸ˜©

Great overview, please send me a pr for documentation with all this! Lets find a proper place for it.

this file https://github.com/cssinjs/jss/blob/master/docs/setup.md seems to be a good fit for this!

You want the whole thing right in setup.md or in a separate file which setup.md links to? The text is maybe a bit lengthy to be placed in full in the setup instructions?

You are right, it would be nice to have a separate csp.md file and then additionally a section on setup.md with ## title and a link to the file below, unless you can make it a bit more compact buy throwing parts out which are specific to your case.

This turned out longer than I thought, so please bear with me.
I think this is possible with server side rendering / templating.

First we create our index.html with a json block that has a placeholder for a nonce.

<!-- index.html -->
...
<head>
  <script data-jss-selector="jss-settings-json" type="application/json">
    {
      "jssNonce": "{{ noncePlaceholder }}"
    }
  </script>
</head>
...

Then if jss can check for the existence of this value at runtime it can use it.

// in jss library - DomRender.js - https://github.com/cssinjs/jss/blob/8252b25dfa7ebdb9ab9112c6760279cd79e742d2/src/renderers/DomRenderer.js#L281
// replace this line
// const nonce = global.__webpack_nonce__
const nonce = getNonceFromInlineJSON()
//...
function getNonceFromInlineJSON() {
  try {
    let inlineJsonBlock = document.querySelector('script[type="application/json"][data-jss-selector="jss-settings-json"]');
    let config = JSON.parse(inlineJsonBlock.textContent)
    let nonce = config.jssNonce
    return nonce
  } catch (err) {
    // best effort
  }
}

The value can be set at every request by using some kind of templating engine. I'll use Nunjucks for this example.

// server.js
const express = require("express");
const expressNunjucks = require("express-nunjucks");
var csp = require("helmet-csp");
var uuidv4 = require("uuid/v4");

const app = express();

// middleware to generate a unique uuid for every request
app.use(function(req, res, next) {
  res.locals.nonce = uuidv4();
  next();
});

// middleware to set the CSP header
app.use(
  csp({
    directives: {
      styleSrc: [
        "'self'",
        function(req, res) {
          // looks like 'nonce-614d9122-d5b0-4760-aecf-3a5d17cf0ac9'
          return "'nonce-" + res.locals.nonce + "'";
        }
      ]
    }
  })
);

// use some templating engine to fill in the nonce placeholder
app.get("/", function(req, res) {
  res.render("index", { noncePlaceholder: res.locals.nonce });
});

Edit: I should add that this works even with a CSP that prevents inline javascript because the script tag has type="application/json"

Edit 2: change scriptSrc to styleSrc. Add note about line referencing webpack. Change to uuid/v4.

@Graham42 In your CSP middleware you apply the nonce to “script-src”. I think you meant “style-src”. Why don’t you just create a second nonce for the script tag and set window.__style_nonce__ = { styleNonce }; I don’t understand why webpack has ANYTHING at all to do with CSP... You generate the nonce in middleware, add it to the header and add it to the html response. There shouldn’t be a nonce in your bundles!

You should use UUID V4 as you want an unpredictable nonce, not a unique one. And CSP asks for a base64 encoded nonce (I just base64 encode a uuid v4)

Thanks for the catch! I meant "style-src", will edit.

Definitely agree that webpack shouldn't have anything to do with CSP, I was referencing the way the code currently works, but arguable it should just be changed. I'll edit and make a note.

Re: the inline script tag with nonce and window.__style_nonce__ = { styleNonce }.
I suppose for this case it would be fine, but I would prefer to encourage a safer pattern.

I'm really not fond of any inline scripts. I can't think of an attack vector for this style nonce case, but in general, the JSON is a safer way to get things from the server. Talking about the general case, if a value gets injected to the server, it won't affect a user visiting the page.

Consider this inlined JS result:

<script type="text/javascript" nonce>
window.__my_app_data  = alert('xss')
</script>

vs this inlined JSON result that we would use JSON.parse with

<script type="application/json" data-my-app="config">
{
  "someData": alert('xss')
}
</script>

In the first case, we have a successful XSS attack. In the second case, an error would just get thrown in the code about malformed JSON.
Yes, there's ways to properly escape values, and make sure this doesn't happen. But in case something gets missed, the JSON is going to be safer.

Feel free to send an addition to the documentation on how to use it without webpack!

I could do that.

Thoughts on replacing the webpack way with code similar to my example above? The current webpack way is not secure. The nonce needs to be unique per request, otherwise it can be copied and used.

From MDN: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/style-src

The server must generate a unique nonce value each time it transmits a policy. It is critical to provide an unguessable nonce, as bypassing a resource’s policy is otherwise trivial.

Sure, great we found that out!

Btw. we should also add a note why using webpack for this job is insecure, otherwise this knowledge will be lost!

Released in 9.6.0

Thanks a lot @Graham42 and @dav-is

Using nonce doesn't really offer that much security.

If your going to the effort of implementing something, why not implement hashing (the strongest option ) instead?
https://github.com/slackhq/csp-html-webpack-plugin

@blowsie because hashing only works in static assets. The CSS here is dynamic (to support theming, etc) so you couldn’t add a hash to the header before sending the html.

And using a nonce is actually very secure.

@dav-is AFAIU the nonce is only secure if it cannot be touched by javascript. Which is why it is not accessible by javascript when added to style/script tags and we therefore have to add it to a meta tag.
Since JSS creates styles on the fly - meaning using javascript - it needs to access the nonce and then create a new inline style tag using the nonce. Which any other 3rd party script now could do.

@waldi you should use a different nonce for your style and scripts. Adding the style nonce to a meta tag gives scripts permission to add styles. If you're doing CSS-in-JS, this is desired, otherwise you shouldn't use nonces at all. However, by using the nonce if a malicious user's HTML is rendered, it won't be able to style unless they also get around the CSP for scripts (where the script nonce would never be given to JS)

When your site is executing 3rd party js which is malicious, no matter where you hide the nonce, in a meta tag or bundled in the script, their script can access it anyways.

If someone is able to maliciously run scripts, I'd say you have bigger issues than the attacker being able to change the styles of the page. If the attacker can't run scripts, using a style nonce stops them. The nonce for scripts should never be bundled or placed in a meta tag

Was this page helpful?
0 / 5 - 0 ratings

Related issues

EugeneSnihovsky picture EugeneSnihovsky  Â·  4Comments

dan-lee picture dan-lee  Â·  3Comments

antoinerousseau picture antoinerousseau  Â·  3Comments

glowkeeper picture glowkeeper  Â·  5Comments

AleshaOleg picture AleshaOleg  Â·  3Comments