Bug 110880 - Cross-origin XSLT requests with CORS should be allowed
Summary: Cross-origin XSLT requests with CORS should be allowed
Status: RESOLVED WONTFIX
Alias: None
Product: WebKit
Classification: Unclassified
Component: WebCore Misc. (show other bugs)
Version: 528+ (Nightly build)
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Raphael Kubo da Costa (:rakuco)
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2013-02-26 07:31 PST by Raphael Kubo da Costa (:rakuco)
Modified: 2023-05-12 23:50 PDT (History)
16 users (show)

See Also:


Attachments
First attempt (30.24 KB, patch)
2013-03-05 08:56 PST, Raphael Kubo da Costa (:rakuco)
no flags Details | Formatted Diff | Diff
First attempt, fix test (30.96 KB, patch)
2013-03-06 09:30 PST, Raphael Kubo da Costa (:rakuco)
no flags Details | Formatted Diff | Diff
Remove some leftover (30.29 KB, patch)
2013-03-06 09:38 PST, Raphael Kubo da Costa (:rakuco)
no flags Details | Formatted Diff | Diff
Do not touch SubresourceLoader (30.84 KB, patch)
2013-03-14 14:43 PDT, Raphael Kubo da Costa (:rakuco)
no flags Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Raphael Kubo da Costa (:rakuco) 2013-02-26 07:31:19 PST
These days, if one attempts to load an XSLT resource from a different origin (as described in <http://www.w3.org/wiki/CORS#xml-stylesheet_processing_instruction_.28XMLSS.29>), the request fails because the origins do not match.

Revisions r34719 and r38065 seem to implement the current behavior, but they reference bugs I do not have permission to view. r34719's commit message said it made WebKit match Firefox, IE and Opera at the time. These days, loading cross-origin XSLT resources works fine on IE and Firefox (and Opera simply ignored the request).

Before spending some time trying to "fix" the issue, I'd like to know if there's an issue to be fixed at all or if we're happy with the current behavior. FWIW, there's a Chromium bug which references this same issue, <https://code.google.com/p/chromium/issues/detail?id=101152>.
Comment 1 Adam Barth 2013-02-26 10:05:21 PST
I've added you to the bugs in question.  They're old enough that we can probably make them public.  The short answer is that cross-origin XSLT can lead to XSS vulnerabilities.
Comment 2 Raphael Kubo da Costa (:rakuco) 2013-02-27 05:03:40 PST
Thanks. Only now have I noticed that I didn't explicitly mention I had CORS in mind, sorry about that.

My actual question is what to do when http://bar.baz/something.xml sends a request to http://foo.bar/resource.xsl, which send back a proper Access-Control-Allow-Origin header; in this case, Gecko and IE load it, whereas we don't.
Comment 3 Adam Barth 2013-02-27 07:58:10 PST
Does the fetch happen with credentials?  Typically we prefer that people use CORS in "anonymous" mode so they don't accidentally share cookie-authenticated data.  For example, that's the default for the crossorigin attribute on <img>.
Comment 4 Adam Barth 2013-02-27 07:59:20 PST
The short answer is yes, though.  We can use CORS to let web sites open into allowing this behavior.  I'd have to look at the security issue again to remember exactly which resources need to supply CORS headers.
Comment 5 Raphael Kubo da Costa (:rakuco) 2013-02-27 09:22:28 PST
(In reply to comment #3)
> Does the fetch happen with credentials?  Typically we prefer that people use CORS in "anonymous" mode so they don't accidentally share cookie-authenticated data.  For example, that's the default for the crossorigin attribute on <img>.

Given that there's no equivalent to the crossorigin attribute for <?xml-stylesheet>, I think both Gecko and IE perform the request without credentials, but I can check that.
Comment 6 Raphael Kubo da Costa (:rakuco) 2013-02-28 07:39:55 PST
Firefox always seems to send its requests in anonymous mode. IE10, on the other hand, does not really seem to apply CORS' rules in the request: if Protected Mode is disabled (or if only port numbers differ from what I could verify) cross-resource XSLT requests are performed and cookies are sent; the request fails and a warning is written on the console otherwise.
Comment 7 Raphael Kubo da Costa (:rakuco) 2013-02-28 07:59:45 PST
Actually, testing a bit more, Protected Mode does not alter IE10's behavior: in this case, whether it performs the cross-origin request or not depends on the "Access data sources across domains" setting. It does not take the CORS headers into account in any case, though.
Comment 8 Adam Barth 2013-02-28 11:43:09 PST
Doing anonymous requests is probably better so that folks can use Access-Control-Allow-Origin: *
Comment 9 Raphael Kubo da Costa (:rakuco) 2013-03-04 15:40:43 PST
(In reply to comment #8)
> Doing anonymous requests is probably better so that folks can use Access-Control-Allow-Origin: *

I'm currently having some trouble with redirects; namely, the ResourceRequest used when requesting a CachedXSLResource is not the one used when the network backend reports a redirect to the loader. This means that even if I, say, write some code in SubresourceLoader::willSendRequest to call updateRequestForAccessControl() the ResourceRequest that I eventually use in ProcessingInstruction::setXSLStyleSheet() has not been changed, and calling something like passesAccessControlCheck() may fail.

Is this really intentional and I'm missing something obvious? Is one really supposed to change both CachedResourceLoader and something like SubresourceLoader to treat both original requests and redirects (I had thought both went through a similar code path)?
Comment 10 Raphael Kubo da Costa (:rakuco) 2013-03-05 08:56:55 PST
Created attachment 191504 [details]
First attempt
Comment 11 Raphael Kubo da Costa (:rakuco) 2013-03-05 08:59:07 PST
Comment on attachment 191504 [details]
First attempt

There are a few things I don't like in this patch very much: for one, passing a ResourceResponse in setXSLStyleSheet feels weird. Also, performing almost the same origin checks in 3 different places makes me feel I'm doing something wrong.
Comment 12 WebKit Review Bot 2013-03-05 09:54:32 PST
Comment on attachment 191504 [details]
First attempt

Attachment 191504 [details] did not pass chromium-ews (chromium-xvfb):
Output: http://webkit-commit-queue.appspot.com/results/17015011

New failing tests:
http/tests/security/cross-origin-xsl-BLOCKED.html
Comment 13 Build Bot 2013-03-05 15:53:34 PST
Comment on attachment 191504 [details]
First attempt

Attachment 191504 [details] did not pass mac-wk2-ews (mac-wk2):
Output: http://webkit-commit-queue.appspot.com/results/17044024

New failing tests:
http/tests/security/cross-origin-xsl-BLOCKED.html
Comment 14 Raphael Kubo da Costa (:rakuco) 2013-03-06 09:30:18 PST
Created attachment 191768 [details]
First attempt, fix test
Comment 15 Raphael Kubo da Costa (:rakuco) 2013-03-06 09:38:59 PST
Created attachment 191771 [details]
Remove some leftover
Comment 16 Alexey Proskuryakov 2013-03-06 10:15:14 PST
Comment on attachment 191771 [details]
Remove some leftover

View in context: https://bugs.webkit.org/attachment.cgi?id=191771&action=review

> Source/WebCore/loader/SubresourceLoader.cpp:147
> +        if (m_resource->type() == CachedResource::XSLStyleSheet && !m_documentLoader->document()->securityOrigin()->canRequest(newRequest.url()))
> +            updateRequestForAccessControl(newRequest, m_documentLoader->document()->securityOrigin(), DoNotAllowStoredCredentials);
>          m_resource->willSendRequest(newRequest, redirectResponse);

Is this the right place to make this check? It seems like the line below should get us inside CachedXSLStyleSheet, which would be better for XSLStyleSheet specific code than SubresourceLoader.

Additionally, this handling of redirects seems unique - I don't see any similar updateRequestForAccessControl calls elsewhere.
Comment 17 Raphael Kubo da Costa (:rakuco) 2013-03-06 11:41:46 PST
Comment on attachment 191771 [details]
Remove some leftover

View in context: https://bugs.webkit.org/attachment.cgi?id=191771&action=review

>> Source/WebCore/loader/SubresourceLoader.cpp:147
>>          m_resource->willSendRequest(newRequest, redirectResponse);
> 
> Is this the right place to make this check? It seems like the line below should get us inside CachedXSLStyleSheet, which would be better for XSLStyleSheet specific code than SubresourceLoader.
> 
> Additionally, this handling of redirects seems unique - I don't see any similar updateRequestForAccessControl calls elsewhere.

I thought about putting it in CachedXSLStyleSheet too. This would probably mean adding some empty method to CachedResource that's only implemented by CachedXSLStyleSheet. Is that OK?

WRT this looking unique: other places, such as <img> + <canvas>, or XHR, provide means for users to specify the behavior their want. XSL was a special case before by simply discarding cross-origin requests. Were you thinking of something in specific?
Comment 18 Alexey Proskuryakov 2013-03-06 11:57:31 PST
> This would probably mean adding some empty method to CachedResource that's only implemented by CachedXSLStyleSheet.

Wouldn't CachedXSLStyleSheet::willSendRequest() work?

> other places, such as <img> + <canvas>, or XHR, provide means for users to specify the behavior their want

What is the place where other loaders do the equivalent of updateRequestForAccessControl() on redirect? Or do they not need to?
Comment 19 Raphael Kubo da Costa (:rakuco) 2013-03-06 12:10:20 PST
(In reply to comment #18)
> > This would probably mean adding some empty method to CachedResource that's only implemented by CachedXSLStyleSheet.
> 
> Wouldn't CachedXSLStyleSheet::willSendRequest() work?

This would mean either passing m_documentLoader->securityOrigin() to willSendRequest() or calling loader()->documentLoader()->securityOrigin() there. I was a bit afraid of this chain not always being non-NULL, but can give it a try.

> > other places, such as <img> + <canvas>, or XHR, provide means for users to specify the behavior their want
> 
> What is the place where other loaders do the equivalent of updateRequestForAccessControl() on redirect? Or do they not need to?

From what I understood from the behavior of <img>+<canvas> and XHR, at creation time (respectively, by setting the crossorigin attribute in <img> and using XHR.withCredentials) one defines whether all requests should be made without any credentials (which means the original request and any occasional ones created by redirects) are sent without credentials or all of them are sent with credentials.
Comment 20 Adam Barth 2013-03-06 14:53:48 PST
Comment on attachment 191771 [details]
Remove some leftover

View in context: https://bugs.webkit.org/attachment.cgi?id=191771&action=review

This looks like a good start.  My main question is why XSLT needs special handling in subresource loader.

>>> Source/WebCore/loader/SubresourceLoader.cpp:147
>>> +        if (m_resource->type() == CachedResource::XSLStyleSheet && !m_documentLoader->document()->securityOrigin()->canRequest(newRequest.url()))
>>> +            updateRequestForAccessControl(newRequest, m_documentLoader->document()->securityOrigin(), DoNotAllowStoredCredentials);
>>>          m_resource->willSendRequest(newRequest, redirectResponse);
>> 
>> Is this the right place to make this check? It seems like the line below should get us inside CachedXSLStyleSheet, which would be better for XSLStyleSheet specific code than SubresourceLoader.
>> 
>> Additionally, this handling of redirects seems unique - I don't see any similar updateRequestForAccessControl calls elsewhere.
> 
> I thought about putting it in CachedXSLStyleSheet too. This would probably mean adding some empty method to CachedResource that's only implemented by CachedXSLStyleSheet. Is that OK?
> 
> WRT this looking unique: other places, such as <img> + <canvas>, or XHR, provide means for users to specify the behavior their want. XSL was a special case before by simply discarding cross-origin requests. Were you thinking of something in specific?

Why is this check needed for XSLStyleSheet but not for CORS-enabled image fetches?

> Source/WebCore/loader/cache/CachedResourceLoader.cpp:238
> +    KURL requestURL = request.resourceRequest().url();
> +    bool sameOriginRequest = m_document->securityOrigin()->canRequest(requestURL);
> +
> +    if (!sameOriginRequest)
> +        updateRequestForAccessControl(request.mutableResourceRequest(), m_document->securityOrigin(), DoNotAllowStoredCredentials);

So, we're only doing an anonymous fetch if the initial URL is cross-origin.  We need to be sure to test the case where a same-origin request gets redirected to a cross-origin URL

> LayoutTests/http/tests/security/cross-origin-xsl-BLOCKED-expected.txt:1
> -CONSOLE MESSAGE: line 2: Unsafe attempt to load URL http://localhost:8000/security/resources/forbidden-stylesheet.xsl from frame with URL http://127.0.0.1:8000/security/resources/cross-origin-xsl.xml. Domains, protocols and ports must match.
> +CONSOLE MESSAGE: Unsafe attempt to load URL http://localhost:8000/security/resources/forbidden-stylesheet.xsl from frame with URL http://127.0.0.1:8000/security/resources/cross-origin-xsl-BLOCKED.xml. Domains, protocols and ports must match.

Why are we losing the line number?
Comment 21 Raphael Kubo da Costa (:rakuco) 2013-03-11 13:39:11 PDT
Sorry for the delay, I was flying across continents.

(In reply to comment #20)
> This looks like a good start.  My main question is why XSLT needs special handling in subresource loader.

As I see it XSL fetches behave differently than the rest because we're sort of adapting the behavior of something that does not really know about CORS, that is, in this case there's no "crossorigin" tag as there is for the <img> tag.

> > Source/WebCore/loader/cache/CachedResourceLoader.cpp:238
> > +    KURL requestURL = request.resourceRequest().url();
> > +    bool sameOriginRequest = m_document->securityOrigin()->canRequest(requestURL);
> > +
> > +    if (!sameOriginRequest)
> > +        updateRequestForAccessControl(request.mutableResourceRequest(), m_document->securityOrigin(), DoNotAllowStoredCredentials);
>
> So, we're only doing an anonymous fetch if the initial URL is cross-origin.  We need to be sure to test the case where a same-origin request gets redirected to a cross-origin URL

This is what http/tests/security/cross-origin-xsl-redirect.html is testing, and why I had to add code to SubresourceLoader: when the original request is a same-origin one, updateRequestForAccessControl() is not called. The platform-specific networking code then processes the redirect, creates a ResourceRequest based on the existing one and calls SubresourceLoader.

The test I added there checks if this is happening and strips the request accordingly.

> >>> Source/WebCore/loader/SubresourceLoader.cpp:147
> >>> +        if (m_resource->type() == CachedResource::XSLStyleSheet && !m_documentLoader->document()->securityOrigin()->canRequest(newRequest.url()))
> >>> +            updateRequestForAccessControl(newRequest, m_documentLoader->document()->securityOrigin(), DoNotAllowStoredCredentials);
> >>>          m_resource->willSendRequest(newRequest, redirectResponse);
>
> [...]
>
> Why is this check needed for XSLStyleSheet but not for CORS-enabled image fetches?

The CORS policy for image fetches is defined by the "crossorigin" attribute in <img>. Specifically, if crossorigin is "anonymous" the original request is already stripped so the RequestResource created after the redirect does not allow cookies either. Analogously, if crossorigin is set to "use-credentials" none of the requests have the omit credentials flag set.

> > LayoutTests/http/tests/security/cross-origin-xsl-BLOCKED-expected.txt:1
> > -CONSOLE MESSAGE: line 2: Unsafe attempt to load URL http://localhost:8000/security/resources/forbidden-stylesheet.xsl from frame with URL http://127.0.0.1:8000/security/resources/cross-origin-xsl.xml. Domains, protocols and ports must match.
> > +CONSOLE MESSAGE: Unsafe attempt to load URL http://localhost:8000/security/resources/forbidden-stylesheet.xsl from frame with URL http://127.0.0.1:8000/security/resources/cross-origin-xsl-BLOCKED.xml. Domains, protocols and ports must match.
>
> Why are we losing the line number?

The line was shown because the document was still being parsed when the loading stopped and the message was logged. As the code in CachedResourceLoader::canRequest() has been removed, the message is added after parsing finishes.
Comment 22 Raphael Kubo da Costa (:rakuco) 2013-03-14 14:43:11 PDT
Created attachment 193186 [details]
Do not touch SubresourceLoader
Comment 23 Raphael Kubo da Costa (:rakuco) 2013-04-02 14:07:11 PDT
Ping?
Comment 24 Alexey Proskuryakov 2013-04-15 15:57:54 PDT
Comment on attachment 193186 [details]
Do not touch SubresourceLoader

View in context: https://bugs.webkit.org/attachment.cgi?id=193186&action=review

> Source/WebCore/dom/ProcessingInstruction.cpp:244
> +    if (!document()->securityOrigin()->canRequest(response.url())
> +        && !passesAccessControlCheck(response, DoNotAllowStoredCredentials, document()->securityOrigin(), errorDescription)) {

It feels strange to see an access check here, this is not loading code. I think that this may be too late.

> Source/WebCore/loader/cache/CachedResourceLoader.cpp:238
> +    if (!sameOriginRequest)
> +        updateRequestForAccessControl(request.mutableResourceRequest(), m_document->securityOrigin(), DoNotAllowStoredCredentials);

I think that this is insufficient to make the request anonymous. ResourceHandleClient should return false from shouldUseCredentialStorage() to prevent ResourceHandle from using credentials stored in Keychain.
Comment 25 Andreas Kling 2014-02-05 11:20:23 PST
Comment on attachment 193186 [details]
Do not touch SubresourceLoader

Clearing review flag on patches from before 2014. If this patch is still relevant, please reset the r? flag.
Comment 26 alexbuzzbee 2019-12-17 14:52:49 PST
This bug still appears to be unfixed, at least in current iOS WebKit.
Comment 27 Anne van Kesteren 2023-05-12 23:50:30 PDT
I don't think we should add support for this unless it's properly standardized. Only Gecko supports this at the moment after all.