W3C home > Mailing lists > Public > public-webappsec@w3.org > June 2016

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

From: Brad Hill <hillbrad@gmail.com>
Date: Fri, 03 Jun 2016 16:31:51 +0000
Message-ID: <CAEeYn8iP2U8z_ZdgYvMbzbpvB7rujLJuSvLPf=HZis186x1c8w@mail.gmail.com>
To: Artur Janc <aaj@google.com>
Cc: WebAppSec WG <public-webappsec@w3.org>, Christoph Kerschbaumer <ckerschbaumer@mozilla.com>, Daniel Bates <dabates@apple.com>, Devdatta Akhawe <dev@dropbox.com>, Mike West <mkwst@google.com>
Ah, yes, a very smart approach I hadn't considered.  :)  (and I'm not
interested in preventing very determined insiders from skirting the policy
as much as I appreciate the opportunity for a conversation presented by
"hey, can you update the CSP to support my new featureX?")

Are people interested in having the dynamic behavior propagate from scripts
identified by a hash as well as a nonce?

On Thu, Jun 2, 2016 at 7:05 PM Artur Janc <aaj@google.com> wrote:

> On Fri, Jun 3, 2016 at 2:21 AM, Brad Hill <hillbrad@gmail.com> wrote:
>> <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.
> One thing to note is that if your policy includes a nonce a developer can
> already get around it -- they can grab a nonce from any script on the page,
> set it on a dynamically created script node, and load arbitrary scripts
> from outside of the whitelist. So I'd argue that you are catching only
> developers who aren't even seriously trying ;-) (which is probably still
> valuable in many cases)
> That aside, I agree this is an important concern, and we've very recently
> had discussions with several teams at Google who use CSP in the same way; I
> know Dev also wants to keep enforcing the script-src whitelist in his apps
> for this reason. The good news is that you can already do this alongside
> 'unsafe-dynamic' by setting two separate policies (this approach is
> courtesy of Dev as well):
> script-src 'unsafe-inline' https://cdn.example.com
> https://geocaptcha.example.com
> script-src 'nonce-123456' 'unsafe-dynamic'
> The user-agent will check each script against each policy separately (
> https://www.w3.org/TR/CSP2/#enforcing-multiple-policies). The first
> policy will only allow external scripts from the whitelisted domains and
> the second one will only allow scripts with a nonce (and their
> non-parser-inserted descendants); so you get both nonce enforcement and you
> make sure that all external scripts come from a whitelisted source. To
> borrow your example:
> <html>
> <!--  Allowed: unsafe-inline (#1) and nonce (#2) -->
> <script nonce="123456">
>   // Allowed: cdn.example.com (#1) and 'unsafe-dynamic' (#2)
>   var s = document.createElement('script');
>   s.src = 'https://cdn.example.com/dependency.js';
>   document.head.appendChild('s');
> </script>
> <!-- Allowed: geocaptcha.example.com (#1) and nonce (#2) -->
> <script src="https://geocaptcha.example.com/geocaptcha.js" nonce="123456">
> </script>
> <!-- XSS vuln here -->
> <!-- Blocked: cdn.example.com (#1) but not policy #2 (no valid nonce) -->
> <script src="https://cdn.example.com/csp_bypass.js"></script>
> <!-- /XSS vuln -->
> </html>
> A developer who already has a whitelist-based policy should be able to
> deploy this by adding nonces to all scripts and just setting another CSP
> header, without any changes to the existing policy. Also, in a production
> scenario the second policy would likely need to have other keywords for
> backwards compatibility (script-src 'unsafe-inline' 'nonce-123456' http:
> https: 'unsafe-dynamic')
> There are currently some bugs in Chrome and Safari related to handling
> nonces when multiple policies are defined, but the general approach should
> work. It's less flexible than the proposal you outlined, because you can't
> grant any scripts the ability to bypass the whitelist (like you would with
> dynamic-entrypoints) but perhaps it's workable?
> Cheers,
> -Artur
>> 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 16:32:32 UTC

This archive was generated by hypermail 2.4.0 : Friday, 17 January 2020 18:54:56 UTC