Closed Bug 1211020 Opened 9 years ago Closed 9 years ago

FF is not Executing CORS Preflight for Cross Domain XHR POST if Content-Type includes text/plain (but is not actually text/plain)

Categories

(Core :: DOM: Security, defect)

41 Branch
Unspecified
macOS
defect
Not set
normal

Tracking

()

RESOLVED DUPLICATE of bug 1210302

People

(Reporter: charles_overbeck, Unassigned)

Details

(Keywords: csectype-sop, sec-high)

Attachments

(1 file)

Attached file crossdomainpost.html
User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36

Steps to reproduce:

- Opened the attached file crossdomainpost.html in Firefox, which does a cross-domain POST to http://www.google.com. I tried opening it both using the file uri and serving it from a local web server.
- Opened Web Developer | Network so I can see network calls from the browser
- Clicked the Submit Request button


Actual results:

FireFox executed the POST (the request failed with a 405 response, but that's irrelevant for purposes of this bug).


Expected results:

Since this is a cross-domain POST, FireFox should have first executed an OPTIONS to see if the resource supports CORS. Since the OPTIONS for that resource returns a 405, it should have never proceeded to execute the POST.

Both Chrome and Safari execute OPTIONS and don't proceed any further. Didn't try IE, since I'm not on Windows.
Component: Untriaged → Security
OS: Unspecified → Mac OS X
Just realized that in the my example html, this line is significant:

xhr.setRequestHeader("Content-Type", "application/json, text/plain, */*, charset=utf-8");

This is not a valid Content-Type, per http://greenbytes.de/tech/webdav/rfc2616.html#rfc.section.14.17, but it goes through.

According to https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Preflighted_requests, text/plain requests are not pre-flighted. If I remove the "text/plain" portion of the Content-Type, then the request does get preflighted -- an OPTIONS call is made.

However, when I view the request in my web server, it gets interpreted as application/json.

The combination of FireFox treating the request as text/plain and my server treating it as application/json make my web app vulnerable to CSRF.
Summary: FF is not Executing CORS Preflight for Cross Domain XHR POST → FF is not Executing CORS Preflight for Cross Domain XHR POST if Content-Type includes text/plain (but is not actually text/plain)
Group: core-security
Flags: sec-bounty?
Confirmed, and you can see the comment 1 behavior by comparing the attached testcase (which won't work loaded from BMO unless you disable mixed-content blocking) to

data:text/html,<html><body><script>function%20submitRequest(){var%20xhr%20=%20new%20XMLHttpRequest();xhr.open("POST",%20"http://www.google.com");xhr.setRequestHeader("Accept",%20"application/json,%20text/plain,%20*/*");xhr.setRequestHeader("Content-Type",%20"application/json,%20*/*,%20charset=utf-8");xhr.send('{"hello":"world"}');}</script><form%20action="%23"><input%20type="button"%20value="Submit%20request"%20onclick="submitRequest();"%20/></form></body></html>

We seem to be doing the right thing in nsContentUtils::IsAllowedNonCorsContentType() (called from xhr code), that is, checking for the literal type rather than a substring match:
http://mxr.mozilla.org/mozilla-central/source/dom/base/nsContentUtils.cpp#7194

The above routine underlies the places that check in both XHR and Fetch code.

net_ParseRequestContentType() also seems to be doing the right thing -- if it finds a ',' in the header it returns an empty value. Is someone not checking for this? but an empty string is never going to positively match "text/plain".

When we send the header (in the case where we bypass the pre-flight) we definitely have the comma-separated list.

FWIW I tested the other two "safe" content-types, "application/x-www-form-urlencoded" and "multipart/form-data", and got the same behavior: no pre-flight if they're present, pre-flight if they aren't.

"text/plain" will trigger simple-header behavior if it's followed by a space or comma, you can put anything you want after that point. "text/plainish" won't work, "text/plain ish" will. If there is something in front of "text/plain" then it must be delimited by a comma (and optional space). You can't use only space in front like you can after.
Group: core-security → dom-core-security
Status: UNCONFIRMED → NEW
Component: Security → DOM: Security
Ever confirmed: true
Product: Firefox → Core
dup of 1210302?
Does this still reproduce? We fixed bug 1210302 in Firefox 42 and this may be a duplicate of that reported issue.
Flags: needinfo?(charles_overbeck)
It no longer reproduces in Firefox 42 -- it is fixed.
Status: NEW → RESOLVED
Closed: 9 years ago
Flags: needinfo?(charles_overbeck)
Resolution: --- → DUPLICATE
Flags: sec-bounty? → sec-bounty-
Group: dom-core-security
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: