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

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

From: Devdatta Akhawe <dev.akhawe@gmail.com>
Date: Fri, 3 Jun 2016 10:39:36 -0700
Message-ID: <CAPfop_24ceSx-Ezn1j3-WX5M2GpfososNi64XwBMcGa8XTvV1g@mail.gmail.com>
To: Brad Hill <hillbrad@gmail.com>
Cc: Artur Janc <aaj@google.com>, 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>
My understanding was that 'unsafe-dynamic' will just allow all code
inserted (not parser generated) scripts to just run. The browser doesn't
really care where the original script that inserted the new script tag came
from. So, script-src https://foobar.com/ 'unsafe-dynamic' will allow
inserting a script tag pointing to cdn.foobar.com too. Same with hash-src,
nonce-src etc etc.


--dev

On 3 June 2016 at 09:31, Brad Hill <hillbrad@gmail.com> wrote:

> 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 17:40:25 UTC

This archive was generated by hypermail 2.3.1 : Monday, 23 October 2017 14:54:20 UTC