- From: Dirk Pranke <dpranke@chromium.org>
- Date: Thu, 13 May 2010 17:37:06 -0700
- To: public-webapps <public-webapps@w3.org>
I mentioned earlier that I would attempt to provide a concrete use case for CORS. Here it is; I suggest that this text be used as a basis for part of the "security considerations" section of the spec. If my example and the analysis is incorrect, or if this is not in fact an intended use case for CORS, please speak up :) The CORS "Use Cases" section cites "a service such as a news or stock ticker can be on a central server and shared with many other servers" as an API that might make use of CORS. Of course, it mentions the HTML 5 eventsource, which I am not very comfortable with, so I will attempt to describe a concrete example using something I am familiar with. I don't think my example changes anything materially and can be deployed using otherwise existing browser technology. There are two variants of such a service - either the service provides generic, anonymous information (i.e., current stock prices), or it provides customized, sensitive information. In the former case, the service does not require authorization or presumably any real credentials, and so the CORS and UMP solutions are essential identical. Problem solved, value added :) In the latter case, assume the service does provide sensitive data. Here's a real example: Yahoo!'s "My Yahoo!" service (my.yahoo.com) allows the user to build a customizable page including data from multiple different services. One option is to include portfolio information from Yahoo! Finance (finance.yahoo.com) (*). Such components are variously called "widgets" or "gadgets", and I assume we are all familiar with this concept. (Google's iGoogle does the same thing, as do lots of other services). Historically, I believe this functionality was implemented (and may still be) using backend service-to-service communication (so all browser requests went from client to my.yahoo.com servers). It would also be possible to create the desired user experience by embedding an IFRAME pointing to a URL on finance.yahoo.com that returned the same content. I understand one desired usage of CORS is to allow us to remove this IFRAME or backend communication, so that the client can directly pull and reformat the data, and so that finance.yahoo.com need only provide a data feed. Here is how I would imagine a regular web developer (meaning, me) would attempt to solve the problem using CORS and UMP. The initial, insecure CORS solution is straightforward ... a "gadget" running on My Yahoo! sends an XHR with the users' credentials to "http://finance.yahoo.com/api/v1/my_portfolio" and gets some JSON back. The insecure UMP solution is similarly obvious, you just replace the URL with "http://finance.yahoo.com/api/v1/portfolio/$USERID" . Given that Yahoo! Finance uses cookies today to identify the user, the CORS solution is trivial to implement. The UMP solution requires My Yahoo! to develop some way of translating $UNGUESSABLE_ID into a user id; this could presumably be almost as easy as stuffing the session id from the cookie into the token. This is slightly more work than the CORS solution, but not a lot more. Now, what are the security implications of these two designs? One interesting aspect of the CORS solution is that there is no way for the CORS-based gadget to get another user's data; it is simply not possible to request it, since a different set of cookies cannot be included in the request. The CORS solution requires you to believe in the security of proper cookie handling. With an HttpOnly cookie, cookies can be handled pretty safely but I imagine all of us are fairly aware that cookies get exposed all the time and hence this is hardly a "perfect" solution, maybe just "good enough". With UMP, on the other hand, the gadget can request any user's data that can be guessed. The obvious answer to this is to change $USERID to an $UNGUESSABLE_ID, and ensure that $UNGUESSABLE_ID isn't leaked. It is a debatable point as to whether ensuring this is easier than ensuring the cookies aren't leaked. One could also observe that with the naive implementation of the CORS API, *any* site could trivially fetch the user's portfolio data, which is presumably not desirable, and so Yahoo! Finance would also need to check the Origin: header. With the UMP solution, this is not strictly necessary but might be a useful "defense in depth". The disadvantage of requiring the Origin header means that this is not really an "Open" API - Yahoo! Finance has to whitelist the callers. Not very web friendly, but this is maybe okay since this particular API was never intended to be Open. Similarly, UMP requires the caller to somehow obtain $UNGUESSABLE_ID. Doing so is undoubtedly some more work than not needing to do so; whether or not this would be more work than maintaining a whitelist would depend on the implementation of both and is outside the scope of this note. Given all this, both designs are relatively okay as long as you trust what is running on My Yahoo!. Now suppose that My Yahoo! allows the user to install third-party gadgets. Suddenly neither Yahoo! Finance nor the browser can distinguish a safe request from a trusted gadget from an unsafe request from an untrusted gadget. Since the CORS solution uses well-known URLs, it is now helpless against exposing this data to a third party. How can we protect against that? One solution would be to run the third-party gadget in an IFRAME (and from a different domain), again. But this would partially defeat the goal we started with in the first place. Another approach would be to attempt to inspect the widget code (either by a human or by something like Caja) and only allow appropriately sanitized code to execute. However, given that the URL is just a string, I suspect it would be difficult to write a general purpose sanitizer to protect against this, or at least to do so and allow the resulting sanitized gadget to do anything very interesting. Maybe a human could do it correctly - this is more or less the definition of "trusted code", after all. What about the UMP-based solution; is it vulnerable? If the page containing the third-party gadget does not also contain the Yahoo!-provided portfolio gadget, then the $UNGUESSABLE_ID is not easily obtained, and so, not really. If the page does contains both the third-party gadget and the Yahoo!-provided portfolio gadget, then there would have to be a way to prevent the third-party gadget from being able to crawl the DOM and extract the $UNGUESSABLE_URL. I don't think that that's possible unless you put the third-party gadget in an IFRAME, again. Or, you can again run the third-party gadget through a sanitizer, but in this case we know that you can implement this programmatically, since that's what Caja does. A third option to protect the API would be to modify the Yahoo! Finance API to require an unguessable token even in the CORS case (so you would use cookies + token). This is the analogy to what we do today for XSRF protection. Arguably this is the most secure solution at all, because it would require the token-generation to be compromised (or the URL to be leaked) *and* the cookie to be leaked. Lastly, if we say that the problem is that My Yahoo! should not have allowed third-party widgets, I will observe that the exact same attacks can occur if the page is simply compromised some other way (through XSS, or code embedded in an ad running on the page). The point of this observation is that what we often believe to be trusted code comes back to bite us. So, what can we conclude from this? If I was doing the security review, then I would say that My Yahoo! needs to include unguessable tokens in the API to Yahoo! Finance and run any non-hand-reviewed code through Caja (or a Caja-like thing). Given that, the ability to send cookies maybe buys some additional security, but not much. It's doubtful that one implementation is easier than the other, since either mechanism for generating and verifying unguessable tokens is going to be provided by a library. So, this example does not make a compelling argument that either the ability to send credentials is inherently flawed, but neither does it make things either significantly more secure or significantly easier to use if you know what you are doing in both cases. If anything, the uncredentialled case might be slightly easier to use (one less parameter on the wire). It does suggest (to me) that it's easier to build a credentialled API that is flawed than it is to build a UMP API that is flawed. I think the credentialled solution's problems are more subtle and easier to miss. Perhaps I am unduly biased by my own experience, however. Does anyone disagree with my analysis? Are there other considerations or alternatives that I have not included? -- Dirk (*) Note that while Yahoo! Finance does provide various public APIs today, a call to provide a user's current stock portfolio is not (AFAIK) available.
Received on Friday, 14 May 2010 00:38:02 UTC