- From: Artur Janc <aaj@google.com>
- Date: Sat, 4 Jun 2016 00:10:48 +0200
- To: Devdatta Akhawe <dev.akhawe@gmail.com>
- Cc: Brad Hill <hillbrad@gmail.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>
- Message-ID: <CAPYVjqq=_EgL3gHvmNJ8S4wrFvUbWVRhf55Z+sNyenGujcwupw@mail.gmail.com>
On Fri, Jun 3, 2016 at 7:39 PM, Devdatta Akhawe <dev.akhawe@gmail.com> wrote: > 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. > Pretty much, with the caveat that in the example policy above https://foobar.com/ would be dropped from the whitelist, and no script would be allowed to run: "[in the presence of 'unsafe-dynamic'] host-source and scheme-source expressions, as well as the 'unsafe-inline' keyword-source will be ignored when loading script". Because host-source and scheme-source is ignored, effectively the only way a policy with 'unsafe-dynamic' can allow a script to run is to bless it with a nonce, hash, or by loading a script dynamically (from a script which was allowed to execute via a nonce, hash, or was itself loaded dynamically). > > > --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 22:11:36 UTC