Subject: CORS redirect behavior proposal

From: Collin Jackson <[email-masked]>
Date: Tue, 22 Sep 2009 11:38:46 -0700

Looking at the behavior of existing implementations, we don't seem to
have committed to how to handle redirects in CORS yet:

IE8 (XDomainRequest): Redirects are not supported

Firefox 3.5: If you start off same-origin, you can redirect
cross-origin once, and the Origin header gets added at that point.
Once you're making cross-origin requests, however, further redirects
are not allowed.

Safari 4 and Chrome 3: If you start off same-origin, cross-origin
redirects are blocked. If you start off cross-origin, you can redirect
on that origin, but you can't go to yet another origin. However, this
redirect behavior only works with synchronous requests; if you're
making an asynchronous request you can't do any redirects.

In summary, CORS redirects have not yet been implemented, except for
synchronous requests in Safari and Chrome. Furthermore, the Safari and
Chrome implementation doesn't yet handle the case where one foreign
origin redirects to another. We still have some choice as to what
behavior to specify in this case.

Goals

The first order of business is to make sure that the specification
fixes the DELETE request security issue that Tyler Close pointed out.
Additionally, it would be nice for the Origin header to be usable as a
CSRF defense (eliminating the need for a separate Sec-From header).

Proposal

Same-origin redirects are allowed. Redirects from same-origin to
cross-origin are also allowed. When processing a redirect from one
foreign origin to another, the browser replaces the Origin header with
"null". In this situation, the browser appends a Sec-Redirect-Chain
header that allows sophisticated sites to see the list of origins that
contributed to this request.

Example 1: Suppose a site1.com web page makes an XMLHttpRequest to
site2.com, which is redirected to site3.com. The requests to site2.com
has a site1.com Origin header, but the request to site3.com has an
Origin header of "null". When making a request to site3.com, the
browser adds a Sec-Redirect-Chain header of "http://site1.com,
http://site2.com".

Example 2: Suppose a site1.com web page makes an XMLHttpRequest to
site1.com, which is redirected to site2.com, which is redirected to
site3.com. The requests to site1.com and site2.com have a site1.com
Origin header, but the request to site3.com has an Origin header of
"null". When making a request to site3.com, the browser adds a
Sec-Redirect-Chain header of "http://site1.com, http://site2.com".

Discussion

This proposal defends against the DELETE attack that Tyler pointed
out: Suppose a goodsite.com web page makes a DELETE request to
attacker.com, which is redirected to victim.com. Previously, the
request to victim.com would carry the goodsite.com origin, potentially
confusing the victim site into deleting something. Under this
proposal, the Origin header would be null, so the victim site would
not be confused.

To defend itself against CSRF, a simple site just needs to make sure
the Origin header contains a whitelisted value. A sophisticated site
that uses redirects can look at the Sec-Redirect-Chain and ensure that
it trusts all the origins in the list.

The Sec-Redirect-Chain header is a tradeoff between complexity and
supporting more use cases. If we feel that the Sec-Redirect-Chain
header is too complicated, it could be dropped from this proposal.

---

From: "Anne van Kesteren" <[email-masked]>
Date: Wed, 23 Sep 2009 14:34:35 +0200

> contributed to this request.

I don't think this works well with the preflight result cache. For more  
thoughts on that see this email:

   http://lists.w3.org/Archives/Public/public-webapps/2009AprJun/1000.html

I agree that it would be good to merge Origin and Sec-From. I've been  
thinking about a simplification of CORS that would make this possible  
while avoiding the complexity you run into with the preflight result cache.

My proposal is to make redirects not work for cross-origin requests with a  
preflight. That is, only make them work for simple cross-origin requests.  
For cross-origin requests with a preflight the redirect status codes would  
be the equivalent of a network error so we can in the future make changes  
there.

This would allow us to use CORS for the EventSource object (which uses the  
equivalent of a simple request). It would also make it possible to use it  
for <img>. Basically if the resource sharing check is successful we could  
add a flag to the <img> so that it does not taint the <canvas> allowing  
you to use images from a different server on the <canvas> element while  
keeping the ability to export image data.

For the scenarios where XMLHttpRequest is involved redirects would not  
work for now. Maybe something to address in CORS v2 or maybe it turns out  
it is not really needed.

For simple cross-origin requests Origin would be a space-separated list of  
origins indicating the redirect chain. What order would be best there?

This is more or less on what I'm planning to go with (will wait a day or  
so with specifying to allow for feedback) unless someone has a better idea  
that keeps things relatively simple and works with the preflight result  
cache.

Kind regards,

-- 
Anne van Kesteren
http://annevankesteren.nl/

---

From: Adam Barth <[email-masked]>
Date: Wed, 23 Sep 2009 08:56:05 -0700

On Wed, Sep 23, 2009 at 5:34 AM, Anne van Kesteren <[email-masked]> wrote:
> origins indicating the redirect chain.

When we used this syntax for the Sec-From header, Mark Nottingham
advocated using commas to separate the origins to better align with
other HTTP headers.

> What order would be best there?

I think the simplest thing is to list the origins in the order in
which the user agent encounters them (with adjacent duplicates
removed).

> This is more or less on what I'm planning to go with (will wait a day or so
> with specifying to allow for feedback) unless someone has a better idea that
> keeps things relatively simple and works with the preflight result cache.

That sounds reasonable to me.  I don't quite understand all the
constraints we get from the preflight cache, but the rest sounds fine.

Thanks,
Adam

---

From: "Anne van Kesteren" <[email-masked]>
Date: Thu, 24 Sep 2009 13:17:09 +0200

On Wed, 23 Sep 2009 17:56:05 +0200, Adam Barth <[email-masked]> wrote:
>> For simple cross-origin requests Origin would be a space-separated list  
>> of origins indicating the redirect chain.
>

That would also allow multiple headers to be used I think. Since  
Access-Control-Allow-Origin needs to have an identical value to the Origin  
header I do not think that would work well. Well, it would probably work,  
but would make all the processing a lot more complicated than it needs to  
be. (I'd prefer it to just be a simple string comparison.)

> which the user agent encounters them (with adjacent duplicates

That sounds reasonable.

>> This is more or less on what I'm planning to go with (will wait a day  
>> or so with specifying to allow for feedback) unless someone has a  
>> better idea that keeps things relatively simple and works with the  
>> preflight result cache.
>

We do not really get constraints, but it just does not work with redirects  
at the moment. To explain this:

   BAR redirects to BAZ
   BAR and BAZ grants FOO access

Now when FOO requests BAR a cache entry is stored for BAZ. However, the  
next time FOO requests BAR the cache entry for BAZ is not used.

I guess to make this work a series of request URLs has to be stored rather  
than just the final one. In addition origin has to become a list too. And  
even with this I'm afraid I'm still missing a lot of subtle things.

Deferring this to later seems acceptable though so I'll leave that out for  
now. (Unless someone comes up with a design that does consider all these  
things.)

---

From: "Anne van Kesteren" <[email-masked]>
Date: Thu, 24 Sep 2009 18:00:27 +0200

I have now specified the approach we discussed:

   http://dev.w3.org/2006/waf/access-control/

For simple requests redirects are followed. For other cross-origin  
requests they are the equivalent of a network error. The Origin header is  
a U+0020-separated list of origins. Each time a redirect takes place an  
origin is added to the origin chain if it is not the same as the last  
origin that was added. The Access-Control-Allow-Origin header needs to be  
identical to the value of the Origin header, octet-for-octet.

Let me know if I missed anything or if the draft is unclear.

On Thu, 24 Sep 2009 13:17:09 +0200, Anne van Kesteren <[email-masked]>  
wrote:
> Access-Control-Allow-Origin needs to have an identical value to the  
> Origin header I do not think that would work well. Well, it would  
> probably work, but would make all the processing a lot more complicated  
> than it needs to be. (I'd prefer it to just be a simple string  
> comparison.)
>
>

---

From: Adam Barth <[email-masked]>
Date: Thu, 24 Sep 2009 16:22:34 -0700

On Thu, Sep 24, 2009 at 9:00 AM, Anne van Kesteren <[email-masked]> wrote:
> For simple requests redirects are followed. For other cross-origin requests
> they are the equivalent of a network error. The Origin header is a
> U+0020-separated list of origins. Each time a redirect takes place an origin
> is added to the origin chain if it is not the same as the last origin that
> was added. The Access-Control-Allow-Origin header needs to be identical to
> the value of the Origin header, octet-for-octet.
>

I've updated draft-abarth-origin to match (and switched the header
name back from Sec-From to Origin):

http://www.ietf.org/id/draft-abarth-origin-03.txt

Thanks,
Adam