Re: Finalizing the shape of CSP ‘unsafe-dynamic’

<hat="individual">

I like unsafe-dynamic a lot but it seems that it tries to fix two usability
concerns with CSP in a single construct.

1) Dealing with "widgets" (e.g. a Like button or ReCAPTCHA) where the set
of domains from which script will ultimately be required is unknown and
maybe largely dynamic. (e.g. in the case of google.com -> google.${ccTLD}
redirections)

2) Reducing the attack surface of overly broad whitelists like google.com
which contain many thousands of scripts, some of which may be useful in a
ROP-like bypass of the protections intended by CSP.

It both expands some attack surfaces while contracting others. Is it
valuable to be able to decompose these two goals?  In particular, is it
valuable to be able to express something like, "I want only the scripts on
example.com that are included by this nonce-tagged entry point, but nothing
not on example.com."  And is it maybe valuable to express that side-by-side
with a policy that works exactly as the prototype of unsafe-inline does
today?

Why I ask....

At Facebook, I have found CSP script-src to be extremely useful outside of
protecting from local XSS.  Most modern web applications are terrible at
what I call "origin hygiene".  They import scripts from dozens of origins.
SRI can help reduce this attack surface for static libraries, but CSP plays
an important role, as well.  At Facebook, we don't want our TCB to include
dozens or hundreds of other domains we don't control, but with thousands of
developers working on it, someone will with fair frequency try to add a
runtime javascript dependency on some third party.  CSP lets us catch this
early and enforce our policies.  I would be very hesitant to deploy an
unsafe-dynamic policy as currently specified because it would mean losing
this critical control point.

Let's say a current policy is:

script-src 'unsafe-inline' https://cdn.example.com
https://geocaptcha.example.com 'nonce-abcdefg' 'nonce-123456'

What if we added two new directives: entrypoints and dynamic-entrypoints,
where "entrypoints" allowed trust to propagate from nonce or hash tagged
scriptsf but _only within the whitelist_, and "dynamic-entrypoints" has the
existing unsafe-dynamic behavior?

Now I could express something like:

script-src 'unsafe-inline' https://cdn.example.com 'nonce-abcdefg'
'nonce-123456'; entrypoints 'nonce-abcdefg'; dynamic-entrypoints
'nonce-123456'

For a page like:

<html>
<script nonce="abcdefg">
  var s = document.createElement('script');
  s.src = 'https://cdn.example.com/dependency.js';
  document.head.appendChild('s');
</script>
<script src="https://geocaptcha.example.com/geocaptcha.js
" nonce="123456"></script>
<!-- XSS vuln here -->
<script src="https://cdn.example.com/csp_bypass.js"></script>
<!-- /XSS vuln -->
</html>

In a browser that understands CSP 1.0, unsafe-inline would apply,
everything would run, including the XSS, but the script from
geocaptcha.example.com might break if it redirects to
geocaptcha.example.co.uk.

In a browser that understands CSP 2.0, the behavior would be identical.

In a browser that understands these new directives, the XSS would not run
because it doesn't have a nonce, and so would not be a valid execution
start point under either entrypoint or dynamic-entrypoint directives.  The
inline and geocaptcha.js scripts would run. The dependency.js script would
run because it starts in a valid entrypoint and matches the whitelist.  If
dependency.js tried to load something from evil.com, that load would fail.
  If geocaptcha.js tried to load something from geocaptcha.example.co.uk
(or from evil.com) it would succeed.

If you wanted the current unsafe-dynamic behavior, you could just add a "
dynamic-entrypoints * " directive.  If you just wanted to close CSP
bypasses without expanding beyond your whitelist, you could just add an "
entrypoints * " directive.  Or you can get fancy like the example above.
 (Facebook, for example, based on my concerns expressed above, might use a
nonce-based 'entrypoints' directive to reduce attack surfaces on our site,
but might never use 'dynamic-entrypoints' or use it only with a very
tightly-controlled set of hash-sources.)

But maybe this is way TOO fancy and confusing.

-Brad







On Thu, Jun 2, 2016 at 5:30 AM Artur Janc <aaj@google.com> wrote:

> Hey all,
>
> Based on the discussion in the F2F and subsequent conversations with cc'ed
> folks it seems that most people think that ‘unsafe-dynamic’ [1,2] is a
> reasonable idea. At the same time, some aspects of the current proposal
> (e.g. the name itself) could probably be improved; I’d like to use this
> thread to iron out any remaining bits.
>
> <recap>
> ‘unsafe-dynamic’ is meant to enable the creation of policies that use CSP
> nonces/hashes to allow script execution instead of the current origin-based
> source whitelists, which are almost always bypassable by attackers [3].
> ‘unsafe-dynamic’ makes adopting nonce-based policies easier by allowing
> already executing, trusted scripts to dynamically include additional
> scripts via non-parser-inserted APIs [4], without requiring developers to
> manually pass around the nonce. Additionally, it makes the user agent
> ignore the script-src whitelist to allow the creation of secure
> backwards-compatible policies -- older browsers can keep using the
> whitelist but supporting browsers will rely solely on nonces.
>
> In such a nonce-based approach the developer would specify a policy with a
> script-src containing a nonce and ‘unsafe-dynamic’, and make sure all
> <script> elements in the response have a valid `nonce’ attribute -- in many
> applications this can be much simpler (and more secure) than a
> whitelist-based policy. So far we’ve had very promising results using this
> approach with popular JS widgets [5] and in several Google applications,
> and are working on much broader adoption of this way of using CSP.
> </recap>
>
> The main concerns that folks have brought up are that the name doesn’t
> match the goal of the feature (a policy with ‘unsafe-dynamic’ will be safer
> because it won’t be vulnerable to whitelist bypasses, and the ‘unsafe-’
> prefix could hinder adoption), and that it makes script-src more difficult
> to reason about. A policy with ‘unsafe-dynamic’ might specify
> ‘unsafe-inline’ and a host whitelist for backwards compatibility, and
> depending on the level of the user-agent’s CSP support they will sometimes
> be respected and sometimes ignored.
>
> Overall, I think we have the following options:
>
> 1. Leave ‘unsafe-dynamic’ as-is, name and everything.
> 2. Change the name, but otherwise leave the behavior as specified. Some
> possibly better names that have been thrown around were ‘allow-dynamic’,
> ‘trust-dynamic’, ‘dynamic-nonce’ and my favorite ‘safe-dynamic’ ;-) (okay,
> maybe the last one won’t fly)
> 3. Change the behavior to make the feature more understandable to
> developers. Ideas for this included introducing another directive (rather
> than using script-src), or splitting ‘unsafe-dynamic’ into two keywords
> (e.g. ‘drop-whitelist’ and ‘propagate-nonces’) -- each of those comes with
> its own set of challenges, but they are probably solvable if it seems like
> this is the way to go.
>
>
> My choice would be #2, i.e. to change the name, which AFAIK was the only
> strong concern raised in our discussions, but otherwise leave the behavior
> as currently written in the spec. But if anyone feels strongly about #3 or
> has ideas for a cleaner way to accomplish the same goal, it would be great
> to discuss it now.
>
> Cheers,
> -Artur
>
> [1] https://w3c.github.io/webappsec-csp/#unsafe-dynamic-usage
> [2]
> https://lists.w3.org/Archives/Public/public-webappsec/2016Feb/0048.html
> [3]
> https://github.com/cure53/XSSChallengeWiki/wiki/H5SC-Minichallenge-3:-%22Sh*t,-it's-CSP!%22
> In a recent review of CSP deployments in the wild we observed that 75% of
> whitelists contain origins which allow such CSP bypasses because of
> Angular/JSONP endpoints. As a result, >95% of current CSP policies offer no
> benefit from markup injection/XSS.
> [4] https://www.w3.org/TR/html5/scripting-1.html#parser-inserted
> The reason to allow only non-parser-inserted APIs to be in scope of
> ‘unsafe-dynamic’ is that parser-inserted APIs (Element.innerHTML,
> document.write()) tend to suffer from XSS, which would allow attackers to
> still exploit such bugs.
> [5] https://csp-experiments.appspot.com/unsafe-dynamic
>

Received on Friday, 3 June 2016 00:22:16 UTC