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

Hash-based CSP support #814

Open
onpaws opened this issue Aug 6, 2018 · 16 comments
Open

Hash-based CSP support #814

onpaws opened this issue Aug 6, 2018 · 16 comments
Labels
complexity:high Best brains need to talk about it. enhancement No kittens die if we don't do that.

Comments

@onpaws
Copy link

onpaws commented Aug 6, 2018

Shout out to the lovely humans who did some great CSP work so far and even shipped documentation. I was recently able to get CSP working on my project thanks to this, cheers.

So this is a great start, but because my app is 100% static [1] I'd prefer to calculate everything ahead of time and avoid the dynamic nonce injection/Helmet approach, if possible. In other words I'd like to support this CSP flavor - do a search for hash-algorithm.

Thus I'm now trying to understand the basis by which JSS injects these <style> tags, e.g.
image

First I thought if it were possible to force JSS to inject a single <style> tag into the document, I could hash that ahead of time. Then I read the material-ui docs which discuss performance reasons for splitting styles across multiple tags, and now I'm curious to learn about the tradeoffs and configurability here.

A possible but clunky alternative is that according the Google CSP evaluator we can define multiple hashes inside one style-src string.
style-src 'self' 'sha256-z124...' 'sha256-2g7e...';

Perhaps this is all a terrible idea and that's OK. Still, even if academic, I'd love to learn what it'd take to arrange this. Hope I'm making sense and thanks for a lovely library either way!

[1] My app is based on CRA and doesn't need to support user-uploaded stuff -- in theory, we should be able to keep everything static, which will enable "dumb" CDN hosting -- fastest page loads at the lowest possible cost.

@onpaws
Copy link
Author

onpaws commented Aug 10, 2018

Ok, in doing some initial poking around I see insertRule is where this appears to happen.
Now trying to zoom out a bit and better understand how/when this gets invoked at runtime...

@onpaws
Copy link
Author

onpaws commented Aug 20, 2018

Curious if anyone is willing to voice opinions on this?

I find this idea appealing, because it would allow decoupling the otherwise static FE bundle from depending on dynamic nonce injection.

That's nice because that re-opens the door to CDN based hosting.

@HenriBeck
Copy link
Member

If you are willing to create a PR for this, we would be open for it.
I or @kof don't have the time right now to look at this feature.

You would need to implement it, create tests and the documentation for it.

I guess the implementation should be relatively easy, though this approach wouldn't work where a StyleSheet is linked (dynamic). Also, your implementation shouldn't include any hashing libaries by default; the user would need to be able to pass/configure them, so they aren't installed for someone who doesn't use this feature.

@blowsie
Copy link

blowsie commented Aug 22, 2018

This might be of some use to you
https://github.com/slackhq/csp-html-webpack-plugin

@onpaws
Copy link
Author

onpaws commented Aug 22, 2018

Thanks for sharing that, will take a look.

Starting tomorrow I'm going to be away for approx. the next 2 weeks but intend to resume looking at this when I get back. If anyone has enough interest to take a crack meanwhile, that'd be great!

More when I'm back :)

@HenriBeck HenriBeck added enhancement No kittens die if we don't do that. complexity:high Best brains need to talk about it. labels Sep 12, 2018
@dav-is
Copy link

dav-is commented Jan 8, 2019

Unfortunately I don't think something like this is scalable. You would need to list a hash of every style block you add. The CSP header would become huge in a large application.

@onpaws
Copy link
Author

onpaws commented Jan 10, 2019

Thanks @dav-is, yeah I was basically starting to reach the same conclusion.
It appears the CSP header approach presumes you have a limited amount of <style> tags.
One thing I'm curious about is when the security policy is applied. In other words, do the style tags that get injected after page init still have to have CSP headers? Not sure but hope to find out

@dav-is
Copy link

dav-is commented Jan 11, 2019

@onpaws I'm pretty sure new <style> tags need to follow the CSP, but if js adds <element style=""> after the initial load then that style should be applied.

@jameslaneconkling
Copy link

Interesting approach @onpaws . Curious if anyone knows of other solutions for using jss in environments where dynamically rendering html tag content is not an option (e.g. when serving from a CDN). Currently, I'm setting style-src 'unsafe-inline' as a policy, which isn't ideal.

I did find that dynamically rendering the <meta property="csp-nonce" content="{{ styleNonce }}"> tag on the client works, assuming the page already has one or more nonces for inline styles, which is super surprising. E.g.

var nonce = Array.from(document.getElementsByTagName('meta')).slice(0).find((tag) => tag.httpEquiv === 'Content-Security-Policy').content.match(/script-src.*'nonce-(.*?)'/)[1]

var meta = document.createElement('meta')
meta.setAttribute('property', 'csp-nonce')
meta.setAttribute('content', nonce)

document.head.appendChild(meta)

Doesn't that kind of undermine the whole point of CSP rules and nonces? I.e. if malicious code on the client can read the existing nonces, what point does it serve? Presumably if something can inject malicious css, it can also read the existing nonce and convince the browser to interpret the css? Or am I missing something here?

@onpaws
Copy link
Author

onpaws commented Jun 14, 2019

Caveat: I haven't actually tried this yet.

If CloudFlare is an option, it sounds like the Worker product might allow rendering dynamic HTML tag content.
Upside, I'm pretty sure you'd get nonce support at scale, downside is vendor lock-in however (not sure who else offers a product quite like this.)

@onpaws
Copy link
Author

onpaws commented Jun 14, 2019

I did find that dynamically rendering the <meta property="csp-nonce" content="{{ styleNonce }}"> tag on the client works, assuming the page already has one or more nonces for inline styles, which is super surprising. E.g.

var nonce = Array.from(document.getElementsByTagName('meta')).slice(0).find((tag) => tag.httpEquiv === 'Content-Security-Policy').content.match(/script-src.*'nonce-(.*?)'/)[1]

var meta = document.createElement('meta')
meta.setAttribute('property', 'csp-nonce')
meta.setAttribute('content', nonce)

document.head.appendChild(meta)

Doesn't that kind of undermine the whole point of CSP rules and nonces? I.e. if malicious code on the client can read the existing nonces, what point does it serve? Presumably if something can inject malicious css, it can also read the existing nonce and convince the browser to interpret the css? Or am I missing something here?

That does sound very odd. Any chance you could share a sample? Curious to repro.

@jameslaneconkling
Copy link

The below html file will log the script-src nonce on load

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Security-Policy" content="base-uri 'self'; object-src 'none'; script-src 'self' 'nonce-oXyH+9nGiPmPt5/tBRP2zw=='; default-src 'self'">
  <meta charset="utf-8">
</head>

<body>
  <script nonce="oXyH+9nGiPmPt5/tBRP2zw==">
    var nonce = Array.from(document.getElementsByTagName('meta')).slice(0).find((tag) => tag.httpEquiv === 'Content-Security-Policy').content.match(/script-src.*'nonce-(.*?)'/)[1]
    console.log(nonce)
  </script>
</body>

</html>

I need to read up on the CSP spec more to really understand why this seeming vulnerability exists. I would have hoped that browsers only allow a nonce to be used once, so that malicious code couldn't reuse an existing nonce to inject malicious code. But that doesn't seem to be the case.

@onpaws
Copy link
Author

onpaws commented Jul 2, 2019

I’m not 100% sure I’m understanding you right, and I’m not claiming to be a super CSP expert, but here are my two cents.

The way I understand CSP, its effects are performed before JavaScript runs on the page. Because it’s value is delivered as a header, it is read before the actual page content.
Thus the fact that we can console.log() a nonce from the DOM is immaterial, as I understand it, since the browser has already passed the point where it would matter.
In other words, JavaScript execution happens way later in the page load, after the headers have been parsed and CSP rules applied.

Does that make sense? Or am I maybe misunderstanding you?

@jameslaneconkling
Copy link

Yeah, you're spot on, but a few additional points might illustrate the vulnerability I'm talking about (I think... I might be misunderstanding how CSP mitigates injection attacks)

  • the CSP policy can be set either by the Content-Security-Policy header, or via the <meta http-equiv="Content-Security-Policy" ...> tag. This is important b/c while it is easy to read the meta tag from the DOM, it is not possible for js to read the Security Policy from the header (though a man-in-the-middle attack could read either).
  • a nonce can be reused. If the page includes a nonce for a non-malicious style or source tag, that nonce can be reused for other (potentially malicious) style/source tags (tested on Chrome 74.0.3729.169)
  • I'm also assuming that most attack vectors that can inject malicious js/css can read the DOM.

If all of the above is true (the Content-Security-Policy is set via meta tag, the browser does not complain about reusing nonces, and an injection attack will often have access to the DOM), then an injection attack could read the nonce, add it to the script/style tag of the malicious payload it is trying to execute, and append the tag to the DOM. CSP in this case would not make an injection attack substantially more difficult.

@onpaws
Copy link
Author

onpaws commented Aug 8, 2019

I've never set CSP via meta tag, I'm curious what the use case is, because your assessment makes sense -it seems it would make it less effective.

According to this
"[CSP via meta tag] can't be used for frame-ancestors, report-uri, or sandbox."

@tiennguyen1293
Copy link

I using MUI 4v and i also have the same issue for style-src: unsafe-inline in jss. following :(
I tried with csp-html-webpack-plugin but it's not working.

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
complexity:high Best brains need to talk about it. enhancement No kittens die if we don't do that.
Projects
None yet
Development

No branches or pull requests

6 participants