4

I was just reading this answer to the question Why is the same origin policy so important?

Basically, when you try to make an XMLHttpRequest to a different domain, the browser will do one of two things:

  1. If it's a GET or POST request which meets certain limits (which the makers of the standard have determined to add no extra risk for CSRF attacks) then the browser just passes the request through.

  2. Otherwise, it does what's called a "preflighted request", where it first sends an OPTIONS request instead and only does what you requested if the checks pass for the OPTIONS request.

But a HEAD request won't receive anything other than headers and a status, which I would believe to be at least as secure as an OPTIONS request, yet it would effectively be blocked.

Reading this section of the draft on the Principles of the Same-Origin Policy it mentions that the Location interface is available

There are some exceptions to this general policy. For example, some parts of HTML's Location interface are available across origins (e.g., to allow for navigating other browsing contexts).

Is there a security reason why HEAD is effectively blocked by the same-origin policy?

ian
  • 1,302
  • 11
  • 21
  • Headers and status can be quite valuable. For example, consider `HTTP/1.1 200 OK` vs `HTTP/1.1 403 Forbidden` after using `new Image().src="http://example.com/login?user=username&password=brute"` (simple example to demonstrate my point, complexity may differ in real-world cases). – Rob W Jul 26 '13 at 10:49
  • @RobW in that case yes, but with an XMLHttpRequest the OPTIONS request will be doing *more* than a HEAD request, so why is it blocked for that? – ian Jul 26 '13 at 12:18
  • @lain The OPTIONS request is initiated by the browser, not by the script. When the response doesn't contain the expected CORS headers, the full request is aborted. Note that OPTIONS request is not visible to the caller, so no information is leaked. – Rob W Jul 26 '13 at 12:47
  • @Iain, are you concerned that HEAD is not treated as a simple request verb? Point #1 in the answer you quote is actually incomplete; the simple request verbs include GET, POST, *and* HEAD. – apsillers Jul 26 '13 at 13:26
  • @aspillers I was unaware that answer was complete, thanks. Yes, basically :) – ian Jul 26 '13 at 14:13

1 Answers1

4

There's two issues with cross-domain requests: whether a request makes it to the server and whether a response is visible to the client script that issued the request. So-called "simple" requests (which use a "simple" method and include only "simple" headers) are guaranteed to make it to the server, but their visibility to the client script still depends on appropriate CORS headers. Non-simple requests are not immediately sent to the server; first, the browser sends a "preflight" request (using OPTIONS) and uses the headers in that preflight response to decide whether to send the actual request. This is because non-simple requests might alter server state, and thus could cause damage even if the client was forbidden from seeing the response.

Let's be clear that the result of a preflight OPTIONS request is never made available to the client script. It is only used privately by the browser to decide whether to perform the actual request initiated by the script:

enter image description here

If you wrote a script that tried to make an actual (i.e., client-visible) OPTIONS request, it would be subject to normal CORS rules:

var xhr = new XMLHttpRequest();
xhr.open("OPTIONS", "http://cors-enabled-server.example.com/");
xhr.onload = function(e) { console.log(e); }
xhr.send();

This request will generate a preflight (because OPTIONS is a non-simple method) and the request will fail, unless the server sends a Access-Control-Allow-Methods: OPTIONS response. Note that this generates a preflight OPTIONS request, but that preflight OPTIONS request is never made visible to the script. The script can only see the "actual" OPTIONS request/response, which comes after a successful preflight.

Second, you ask:

But a HEAD request won't receive anything other than headers and a status... yet it would effectively be blocked.

This is not true: the visibility of the result might be blocked, but the request is not. This is because the HEAD method is a simple method according to CORS and thus does not necessitate a preflight, just like GET and POST:

A method is said to be a simple method if it is a case-sensitive match for one of the following:

  • GET
  • HEAD
  • POST

A simple cross-domain HEAD request still requires an acceptable Access-Control-Allow-Origin response, but it does not require a preflight. Therefore, a simple HEAD request (i.e., one that has no special headers necessitating a preflight) will always be sent directly to the server.

OPTIONS, on the other hand, is a non-simple method and must be approved by Access-Control-Allow-Methods in a preflight response. Do not confuse a preflight OPTIONS request (which is never subject to CORS, but is visible only to the browser, not the script) with a JavaScript OPTIONS request (which is visible to the client, but only if it passes CORS requirements).

To answer the title question in your title, "How could a JavaScript making a cross-domain HEAD request be a threat?": it's a threat roughly on the order of allowing a cross-domain GET request, since HEAD should be --according to the HTTP spec -- the same as making a GET request, but without the response body. There's still the potential for lots of sensitive data to leak through headers and status codes.

apsillers
  • 5,780
  • 27
  • 33
  • Thanks for being so thorough. It wasn't so much that I wanted to see the preflight OPTIONS, I understood that it would probably be done in the background, more that I couldn't see why a HEAD was considered in the same kind of threat level as a GET or POST, but I see what you're saying. Your taking the time to answer is much appreciated. – ian Jul 26 '13 at 15:21