In Part 1 of this series, I described how Same Origin Policy prevents web content delivered from one origin from reading content from another origin. (If you haven’t read that post yet, please do start there.)
In today’s post, we’ll look at what restrictions are placed on writing between origins.
What is a “Write”?
For the purposes of this post, to “write” means to send content from one origin to another. Writes could take any of the following forms:
- Navigating to a URL (especially with a query string parameter)
- Uploading a file or performing a HTTP POST using a web form, XMLHTTPRequest, or XDomainRequest
- Manipulating a property of a frame
- Writing content to a frame’s document or manipulating a DOM object in that document
- Sending a message to another frame using postMessage
Some forms of cross-origin write are permitted in the Same Origin Policy, and others are not.
Why is Cross-Origin Writing Ever Permitted?
Given the significant restrictions imposed by Same Origin Policy on cross-origin reads, it may be surprising that SOP allows cross-origin writes at all. However, consider what the web would look like without cross-origin writes—every website would act as an isolated sandbox, with no way to send data to other sites and services. Most “mashups” would be impossible, because each web site would run within a “silo,” able to communicate only with its own origin.
While prevention of cross-origin writes would mitigate entire classes of web security vulnerabilities (CSRF, XSS, etc), the web is a far richer platform because some forms of Cross-Origin Writes are permitted in most cases. Of course, the possibility of receiving a malicious request from any attacker presents a significant defensive burden on a server and the documents it serves—every write must be scrutinized closely to prevent malicious input from corrupting the recipient.
Generally speaking, any web page may navigate to any other, passing whatever URL path, query string, and fragment it wants to. This flexibility is key to the openness of the web, but it’s also the source of many security bugs. Careless site developers frequently fail to validate the input received in a URL or a POST body, and either store that information or echo it back to the client application. This can result in a cross-site scripting (XSS) vulnerability, because that malicious input could contain script or other active content that would run in the security context of the server that replayed or echoed the input.
Another common and dangerous method of attack based on cross-origin writes is called a Cross Site Request Forgery (CSRF, pronounced “Sea Surf”) attack. CSRF attacks rely on the fact that web browsers will present cookies and authentication information to the servers they are communicating with without regard to the context of that communication. The presence of the victim user’s cookies and credentials (sometimes called “ambient authority”) typically means that, as far as the server is concerned, any request from the user’s browser is treated as legitimate. An attacker exploits the user’s ambient authority by crafting a malicious page that causes the user’s browser to issue (usually invisible) attacker-crafted requests to a cross-origin victim server (e.g. a bank, social network, or store). The recipient server often cannot distinguish a request which was intended by the user (e.g. “buy this book”, “subscribe to that person’s postings”) from a request that the user’s browser sent under the direction of markup on a malicious site.
For instance, in this recent exploit, the user’s browser is tricked into making requests to Amazon’s servers using hidden IFRAMEs or IMG tags. Amazon, upon receiving the user’s cookie, associates the requests with the victim user, and subsequently associates the requested pages and images with that user's shopping history. When the user later visits Amazon, the store’s recommendations are used to suggest items that Amazon has related to the CSRF-generated history.
Cross-origin requests can be used to launch password-guessing attacks (e.g. by tricking the user’s browser into sending thousands of requests using a dictionary of common passwords), to determine the user’s browsing history (by profiling cache timings), and to egress information stolen from pages where limited (non-script) injections were possible (e.g. using CSS selectors to invoke url() requests).
Simpler exploits are possible as well—a site might blindly trust the value of a URL parameter (e.g. IsAdmin=True) and expose itself to abuse.
Perhaps the first ever same-origin restriction is that a DOM is not allowed to access most objects in another DOM unless the two DOMs were served from the same fully-qualified domain. One relaxation to that rule (allowing two FQDNs that share a common private domain via mutual opt-in of setting the document.domain property) has been a source of myriad problems over the years.
While the web platform exposed a ton of cross-origin write capability, as browser developers and standards authors realized the dangers such capabilities posed, they “froze” most such capabilities and required explicit opt-in for relaxing the rules beyond the capabilities already irrevocably baked into the platform.
A great example of such restrictions is the development of “Cross Origin Resource Sharing” (CORS) mechanisms like the XDomainRequest and updated XMLHTTPRequest objects. These objects impose significant restrictions to try to limit the risk of their cross-domain functionality. For instance, the XDomainRequest object was designed to never emit credentials and allows only a limited set of methods and headers; the goal was to match the capabilities of HTML4 FORMs. By limiting XDR to what could be emitted from a HTML Form, we could ensure that we were not opening up new CSRF vectors with the object.
Similarly, when the CORS XMLHTTPRequest object adds anything but a very limited set of headers to a request, that request becomes a non-simple request which the server to respond affirmatively to a “preflight” request to confirm that it expects such headers. Similarly, if non-simple methods other than GET, POST, and HEAD are used, a preflight confirmation is required. In this way, the potential for cross-origin abuse can be limited. Unfortunately, the risk isn’t entirely erased; some web applications can still be abused even with the CORS restrictions in place.
Generally speaking, cross-origin requests should not be permitted to submit methods other than GET, POST, and HEAD, to send custom headers, or to issue POSTs with Content-Types other than application/x-www-form-urlencoded, multipart/form-data, or text/plain. Where possible, requests should not be permitted from the Internet to the Intranet or local computer, and the automatic transmission of cookies or credentials should be avoided if at all possible.
Depending on the browser, certain types of cross-origin navigations may not be permitted. For instance, Internet Explorer has a feature called Zone Elevation Protection that prevents navigation between sites of certain security zones (e.g. Internet-Zone pages cannot load Local Machine Zone resources). Internet Explorer also recently blocked navigation to file:// URIs from Internet-Zone pages, and IE10 on Windows 8 blocks loading of Private Network resources from the Internet Zone as well.
Internet Explorer’s P3P mechanism restricts which cookies may be replayed from a 3rd-party context; this is designed to help protect the user’s privacy, but a clever web developer could use this mechanism to help protect itself cross-origin requests bearing the user’s cookies. (For the last few years, I’ve been pondering whether it makes sense to offer a FirstParty attribute on cookies that prohibits the cookie from ever being sent in a 3rd party context; this would likely be simpler for other browsers to implement than a full P3P engine, and it would ensure that even users with ridiculously lax P3P preferences still benefit from the protection.)
The HTML5 postMessage feature was explicitly designed with cross-document cross-origin communications in mind. The targetOrigin parameter allows the caller to specify that only an expected origin may receive its message. Similarly, the recipient may check the event’s origin property to ensure that the sender is a trusted source.
Websites can protect themselves against cross-origin writes by carefully validating input data. For instance, when collecting information that will be stored or echoed, that information should be scrubbed for XSS and SQL injection attacks. Some sites check the request's Referer header, but should never use that as their only defense (since buggy plugins or features might allow an attacker to submit a forged origin header, and some security software strips all Referers). Many sites implement CSRF Tokens, whereby they reject requests that do not bear a one-time token supplied by the server; the token is kept secret by the Deny Read aspect of Same Origin Policy that we covered back in Part 1 of this series.
I'll conclude this series with Part 3: Allow Execute ... hopefully, it will take me less than 32 months to write that one. 🙂
[EricLaw]: Unfortunately, I moved on from Microsoft before I could write the "Part 3: Cross-origin Execute" post. It's the least important of the series, and perhaps I'll write it and post it somewhere else at some point. The key topic of the post is that you have to be very careful when allowing cross-origin execute to avoid "leaking" to allow cross-origin reads. For instance, when executing (displaying an image) you need to make sure it doesn't lead to side-effects like the page being able to see the pixels via some other trick. More importantly, all browsers used to have a bug where you could use a LINK REL=STYLESHEET element to pull in a file cross-site. If the CSS parser was in quirks mode, it would allow an attacker to use the CSS OM to read content from the "sheet." The problem is if the "sheet" wasn't really text/css but was instead a HTML document or other document with important information. Browsers closed this hole by doing things like: http://blogs.msdn.com/b/ie/archive/2010/10/26/mime-handling-changes-in-internet-explorer.aspx