- From: Brad Hill <hillbrad@gmail.com>
- Date: Fri, 03 Jun 2016 16:31:51 +0000
- 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>
- Message-ID: <CAEeYn8iP2U8z_ZdgYvMbzbpvB7rujLJuSvLPf=HZis186x1c8w@mail.gmail.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