widget example of CORS and UMP

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