Recently, someone asked a pretty simple question: “Why doesn’t IE consider the port when evaluating Same Origin Policy?” and I realized that my Same-Origin-Policy series lacks an in-depth look at the concepts surrounding origins.
Table of Contents: Same Origin Policy Posts
- Part 0 – (This post) What’s an Origin
- Part 1 – Deny Read
- Part 2 – Limited Write
- Part 3 – Limited Execute (to be written)
Why do we need origins at all?
The Web is comprised of many different sites (“principals”) – some official and legitimate (e.g. your bank), some outright malicious (phishers) and every shade in between. As a consequence, browsers must keep the code and data between these principals separated, or the “bad guys” could use their code, running in your browser, to attack or steal data from the “good guy” sites your browser can access.
On the web, origins are meant to represent a single security principal, or as I sometimes call it, “a single sphere of control.” You don’t want each individual page to be an isolated origin, or it would be cumbersome to build interesting websites and applications1. On the other hand, you don’t want an origin to encompass resources that are under the control of unrelated entities, or those resources will be able to use the client’s browser to attack one another.
For instance, naïve sites in the early web used to allow different users to serve content from the same host but different path (e.g. //example.com/~userA and example.com/~userB), and security isolation was utterly nonexistent2, because siteA and siteB could scribble all over one another.
Okay, so we want to partition the websites controlled by different entities. Each entity is identified by its “origin.”
Origin ≈ URLMon SecurityId
Within Internet Explorer and (to some extent) across Windows, UrlMon is responsible for managing the access permissions of web content. In most cases, URLMon will end up examining the SecurityId of the source and target resources to determine whether access should be granted to the target. Technically, a SecurityId is an opaque byte array up to 512 bytes long (MAX_SIZE_SECURITY_ID) but, in practice, the SecurityId typically follows the format mentioned in the GetSecurityId method’s documentation:
As you can see, this is similar to, but not exactly equivalent to the recent RFC6454 definition, which defines the typical origin as:
triple(uri-scheme, uri-host, uri-port)
In typical cases, URLMon will check that the source and target Security Ids match exactly, although there’s also a CompareSecurityIds function which performs a more-liberal (and thus potentially-dangerous) comparison.
If the SecurityIds match exactly, access is typically granted; if the SecurityIds don’t match, an override setting (e.g. URLACTION_CROSS_DOMAIN_DATA) may be consulted before access is denied.
So, why isn’t the Port part of URLMon’s SecurityId?
Short answer: I don’t know – it was before my time.
Longer answer: I have two guesses. Guesses can be either right or useful.
Guess #1: In the early days of the Internet, virtually all of the hosts were running some form of Unix-like operating system. Unix was a multi-user OS from the start, and by default, any user could listen on a TCP/IP port (only “low-numbered” ports <1024 were restricted to admins). So, if the concept of an Web Origin didn’t include a port, Unix UserA could stand up a web server and attack Unix UserB. In such a world, the port clearly needs to be a part of the Origin.
However, back in those early days of the web, Windows was basically a single-user operating system, which made the “different users running different servers from the same box” scenario less applicable for a Windows web server. (Of which, there really weren’t many until Windows NT became popular, at which point Windows had become a true multi-user OS). The ports could have been considered a part of the SecurityId at that time, but changing this would break any corporate applications that had been written with the expectation that they were not. For instance, you could have a Intranet Timesheet app running on one port and a Payroll app running on another and it might be desirable to allow them to interact.
Guess #2: When URLMon was first built, it wasn’t entirely clear what protocols would be relevant in the future. As a consequence, URLMon abstracted and generalized many concepts, and “port” is a concept which is specific to network protocols based on IP. As such, it might not have been deemed sufficiently generalized to use as a part of the SecurityId. Furthermore, a protocol implementer could include any non-default port in the host component of the SecurityId even though that never happened.
Could SecurityIds be changed to include the port?
Today, I think a decent argument could be made that IE should consider port to be a part of the origin. That’s especially true now that IE supports CORS for XHR, which means that it would be possible for a site to “fix itself” by offering up an Access-Control-Allow-Origin directive for XHR interactions (although
DOM-interactions would be still be blocked).
However, there are three problems with changing this now:
- Compat. Obviously, this would be a breaking change for any site that was written to ancient IE and never needed to work cross-browser.
- Completeness. In IE8 the native XmlHttpRequest object included port as a part of the same-origin-policy evaluation. This was our first stab at trying to move over to the standards-based SOP. Unfortunately, the outcome was awful—a number of sites refused to use the native XHR and kept using the ancient MSXML XHR object to keep their sites working.
So we didn’t get standards OR security. We reverted the “port as part of origin for XHR” in a future update.
- The problems IE’s behavior causes are mostly “solved”. Largely due to #2, sites have been forced to solve for this issue. And the solution is pretty simple– if you want to host multiple unrelated sites on a single box, and have isolation between them, you simply use multiple hostnames, one per principal, and you only route traffic to the destination port if the hostname is correct. That way, traffic is properly isolated regardless of browser version.
Even if IE12 were to change in behavior to include the port as a part of the origin, sites won’t be able to rely upon such isolation for a very very long time; there are hundreds of millions of legacy clients out there, and an unknown number of extensions and plugins that might never be updated with the new rules.
When we considered making a change in IE8 and future releases, we had to decide: “Is the (unknown) compat hit we’d take today worth the (unknown) security value this would provide at some point in the future?” And our decision in IE8-IE11 was “no.”
Quirks from the Zone in the SecurityId
In most cases, Internet Explorer’s inclusion of the Zone in the SecurityId is redundant, because in most scenarios the Zone of a resource is determined by its hostname, which is already a part of the SecurityId. However, there are two scenarios where two SecurityIds may have the same hostname but a different zone.
A user’s security zone configuration can change at almost any time; a user can simply click Tools > Internet Options > Security and move a site from one zone to another. This is, at best, a rare corner case.
What went wrong?
The IT department had recently updated the Proxy AutoConfiguration Script so that accessing the mail server would go DIRECT, bypassing the proxy. Doing so causes the site to be treated as Intranet Zone. As a consequence, when users moved from work to home or vice versa, the PAC script was either disabled or enabled and the SecurityIds generated for the mail servers resources bounced between Internet and Intranet. The mismatched zone number caused the Same-Origin-Policy checks to fail.
To resolve the issue, we suggested that the IT Department should use Group Policy to force the mail site into the Intranet zone, so that its SecurityId would remain stable regardless of whether or not the proxy script were in use.
Long before the advent of the HTML5 Sandbox attribute, IE6 added the ability for a page to restrict a subframe’s content, by setting an SECURITY=RESTRICTED attribute on the IFRAME. This attribute causes the content of the frame to be treated as if it were running in the Restricted Sites zone, regardless of what hostname it is served from. Interestingly, SOP suggest we shouldn’t be able to use an Internet Zone page to writing into a Restricted Zone page from the same host, but this restriction doesn’t seem to be enforced.
Quirks for File URIs
Typically, the SecurityId consists of a protocol scheme, hostname, and zone number. Hostnames are canonicalized to lowercase and thus most SecurityIds are effectively case-insensitive.
However, file URIs’ SecurityIds consist of:
The first path component is included as part of the SecurityId because file shares are named, and different shares may have different access lists. For instance file://server/Accounting and file://server/Dev/ are considered different origins.
On some file systems, paths and share names are case-sensitive, so as a consequence, the SecurityId of a file URI is also case-sensitive. You will find that file://server/Dev/Page1.htm can interact with file://server/Dev/Page2.htm but not file://server/dev/Page3.htm because the Origin for the first two pages is FILE:server/Dev while the origin for the third is FILE:server/dev.
In a future update, we’ll look at the relationship between origin and:
- cookies (secure & insecure)
- XHR’s Origin header
- relaxation of document.domain
1 Trying to retrofit “fine-grained origins” onto the Web is a risky proposition, as Jackson and Barth explained in their great paper.
2 This is commonly called “The GeoCities problem” but I think it was some other site (maybe AngelFire?) that did this.