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

Enable CSP3 'unsafe-hashes' for script src attributes #574

Open
arturjanc opened this issue Oct 26, 2022 · 4 comments
Open

Enable CSP3 'unsafe-hashes' for script src attributes #574

arturjanc opened this issue Oct 26, 2022 · 4 comments

Comments

@arturjanc
Copy link

tl;dr Let's allow CSP3 'unsafe-hashes' to apply to <script src> attributes to let developers allowlist scripts by their URL, as discussed at TPAC. This will make it possible to build tools to enable CSP on existing websites without requiring application changes.

Background

The script-src and style-src directives support hashes to allowlist resources that developers have explicitly enumerated and want to allow to be loaded in their applications. The 'unsafe-hashes' keyword lets developers use hashes to allowlist existing markup, such as inline event handlers, that would otherwise be incompatible with CSP and require a policy to include 'unsafe-inline'.

All of this makes it possible to create flexible policies based on script hashes. Recent academic research has shown that such policies are generally safe for the majority of websites (see To hash or not to hash: A security assessment of CSP’s unsafe-hashes expression).

However, currently, websites that load external scripts (<script src="...">) need to choose between changing their markup to add dynamic per-response nonce attributes to be compatible with strict CSP, or using significantly less safe allowlist-based policies. (Allowing external JavaScript via hashes by hooking into Subresource Integrity unfortunately isn't practical in most cases because the hash applies to the content of the response, precluding the use of hashes for script responses that are dynamically generated or can otherwise change -- a large amount of popular script-based APIs fall into this category.)

With this change, we'll allow developers to use fully hash-based policies (possibly with 'unsafe-eval' and 'strict-dynamic' where necessary) without requiring application-level changes beyond setting the correct CSP header.

Details

'unsafe-hashes' already makes it possible to use hashes for a number of element attributes that allow script execution (e.g. inline event handlers, such as onclick, or javascript: URIs). This change would allow a <script src=[URL]> to execute if the script-src directive contains 'unsafe-hashes' and the hash of [URL] is present in the list of hashes in the policy.

This has the following consequences:

  1. Unlike with host-source, we will not enforce the policy on each redirect when fetching a script; we'll only require the script's src attribute to have its hash added to the policy. This matches the behavior of setting a nonce attribute on a script -- as long as the original <script> element has the correct nonce, it will be allowed to execute, even if the server redirects to a different URL.

  2. Developers will need to be careful about adding hashes for relative script URLs . To be safe, policies will need to also set a base-uri directive; this is already the case with strict CSP and covered in the Nonce retargeting note section of the spec.

    Note: Instead of applying to the value of the src attribute verbatim, we could consider first resolving the absolute URL and then applying the hash to it. E.g. https://site.example with <script src="/foo.js"> would need to compute hash("https://site.example/foo.js") instead of just hash("/foo.js"). However, this would introduce problems for dev/test/staging environments because the hashes would be different; asking developers to use base-uri seems like a better choice here.

  3. Developers will be able to (and will have to) allowlist legitimately loaded URLs by including all of the parameters present in the request -- adding a hash for https://site.example/foo.js?param=foo will not allow https://site.example/foo.js?param=bar to load. In cases where websites load scripts with dynamically generated script src attributes, they will need to either use script nonces or 'strict-dynamic' in cases where these scripts are created at runtime (e.g. by JS libraries).

We probably also want to do the same for style-src and <link rel=stylesheet href> attributes.

Why use hashes instead of verbatim URLs

See here. In short, the current way URLs work in CSP doesn't work in this case because host-source is checked on every redirect hop and for this reason it can't be granular enough (due to well-known concerns about leaking post-redirect URLs). Currently URLs also aren't compatible with strict-dynamic which, for backwards compability, ignores the host-source allowlist.

An unintended benefit of using hashes is that they'd gracefully handle long URLs and URLs with metacharacters which would be problematic in an HTTP header.

It's arguably still fairly strange to use hashes this way. However, the practical benefit of allowing the adoption of strict CSPs without requiring developers to rewrite their markup seems worth it.

Backwards-compatibility

There are two issues to consider here:

  1. Existing users: 'unsafe-hashes' is not yet widely deployed. The change will make existing policies that use the keyword more permissive; however, it seems unlikely that hashes of e.g. inline event handlers can be used as values of script#src attributes to give potentially injected markup any useful capabilities.
  2. In browsers that implement 'unsafe-hashes' today, but don't apply it to script#src, policies that rely on the use of hashes for script#src will break. Developers will likely need to perform user-agent sniffing when setting such policies.

/cc @mikewest @lweichselbaum

@mikewest
Copy link
Member

I don't think this is crazy, and could certainly enable some deployment improvements as we discussed at TPAC. I'm a little skeptical of the redirect bit in particular as well as the relative URLs, but I'm sure we could work something reasonable out if we talk through it (for example, we could require 'strict-dynamic' in order to enable the redirect-ignoring behavior, treating it as conceptually similar to a script that did nothing other than load the URL that was in its location header)/

/cc @letitz @antosart; @annevk; and @ckerschb, @dveditz, and @mozfreddyb to weigh in from their browsers' respective perspectives.

@letitz
Copy link
Member

letitz commented Oct 28, 2022

This sounds reasonable overall!

In browsers that implement 'unsafe-hashes' today, but don't apply it to script#src, policies that rely on the use of hashes for script#src will break. Developers will likely need to perform user-agent sniffing when setting such policies.

I don't follow this part. Can you elaborate?

@arturjanc
Copy link
Author

for example, we could require 'strict-dynamic' in order to enable the redirect-ignoring behavior

I'm not entirely enthused by this particular approach because 'strict-dynamic' focuses on allowing the execution of scripts created at runtime, and the hash-based approach is conceptually independent of this (i.e. you could have a hash-based policy without 'strict-dynamic', similarly to how you can have a nonce-only policy and ensure to propagate the nonce on all script loads).

I see this as more similar to how script nonce attributes work, i.e. if you add a nonce to <script src="foo.js?callback=bar">, you're permitting the loading of a foo.js?callback=bar script in your application and trust the contents returned by the server (including if the server redirects you to a different resource). Hashing the foo.js?callback=bar attribute value seems like expressing the same kind of trust relationship.

That said, I do think it's good to think about this, e.g. consider whether this should perhaps be controlled by a separate keyword ('unsafe-hashes-for-external-scripts'?) -- my knee-jerk reaction is that we might not need it, but it would also be workable from a developer's point of view.

Developers will likely need to perform user-agent sniffing when setting such policies.

I don't follow this part. Can you elaborate?

Assume browsers become more permissive in their interpretation of 'unsafe-hashes' and allow it to apply to script#src. Then, if a developer sets a policy of script-src 'unsafe-hashes' 'sha256-[hash of "foo.js"]' and enables it on a page with <script src="foo.js">, then today's browsers which support 'unsafe-hashes' will end up preventing the script from loading. So developers would need to take into account the fact that Browser X implemented this behavior in version Y and only set such policies for browsers with at least this version.

@letitz
Copy link
Member

letitz commented Nov 2, 2022

Makes sense, thanks!

I don't terribly see the point of using a new keyword for the redirect support, but if we go that route, I cast my vote for unsafe-url-hashes instead of unsafe-hashes + strict-dynamic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants