A History Leak

Suppose that secret.example.com hosts a page that, for social or other reasons, a user would prefer to be able to visit without this fact being exposed. For example, the site might be subversive or considered blasphemous. The page uses the JavaScript document.cookie object to set a session cookie for some legitimate purpose — possibly, to indicate that the user has agreed to a terms of service dialog or has confirmed that he or she is not a minor. Further, the name, format, or other factors with this cookie are unique enough such that it can easily be identified in the browser’s cookie store from its name and value alone, and its presence means it is highly likely that the user visited the secret.example.com site.

The same-origin policy would normally prevent pages from other servers, such as spy.example.org, from accessing the cookies from other origins. Indeed, as Figure 2 shows, after accessing a page on secret.example.com that sets a cookie using the document.cookie object, viewing a page on spy.example.org that displays all cookies contained in the document.cookie object returns nothing.

Figure 2. The same-origin policy prevents a page on spy.example.org from reading a cookie set by secret.example.com.

Now suppose that instead of accessing the two sites directly, the affected user accesses them through a translation service, instead. I’ve moved the two pages to the public-facing domains demo.securityevaluators.com and demo2.securityevaluators.com rather than secret.example.com and spy.example.org, respectively (for testing purposes), but the same-origin policy still applies². Figure 3 shows how, when both sites are accessed through a translation service rather than directly, pages on demo2.securityevaluators.com can access cookies set by demo.securityevaluators.com that would normally be protected by the same origin policy.

Figure 3. When both sites are accessed through a typical translation service, a page on demo2.securityevaluators.com can access cookies set by demo.securityevaluators.com even though they would normally be isolated by the same-origin policy.

Returning to the earlier terminology, in a real-world attack the page on spy.example.org might scan the current cookies for evidence that the user has accessed secret.example.com, and report that fact back to the server if such a cookie is found. Such “history leaks” are considered a violation of the browser security model, and where they have occurred elsewhere, have been treated as security bugs and fixed (e.g., as in CSS selectors). Because the attack shown here relies on JavaScript’s document.cookie object, rather than HTTP headers, this scenario could arise in any mirroring service, not just those that actively proxy traffic in real time.

A Session Cookie Leak

Now, let’s consider a more consequential attack, in which a user’s (possibly-authenticated) session cookie is leaked between two servers. Unlike the history leak, this attack only works against mirroring services that actively relay traffic between the browser and server, so I concentrate on the Hide.me anonymization service.

Suppose that a user (directly and not through a mirroring service) visits and logs in to a web application on demo.securityevaluators.com, and then visits two attack pages on demo2.securityevaluators.com. One attack page attempts to read the cookie previously set by the web application in the HTTP request headers, while another attempts to obtain it using JavaScript. As shown in Figure 4, the same-origin policy blocks both attacks when a user accesses these two domains directly.

Figure 4. The same origin policy blocks pages on demo2.securityevaluators.com from receiving cookies for demo.securityevaluators.com.

Now suppose that instead of accessing the two sites directly, the affected user accesses them through the Hide.me proxy service, instead. Figure 5 shows three windows, all containing pages accessed through Hide.me. The top window shows an authenticated session in the web application. The middle window shows an attack page (served from a different domain) that gathers cookies from HTTP request headers (i.e., it executes at the server-side). The bottom window shows an attack page that gathers cookies from the JavaScript document.cookie object.

Figure 5. When legitimate pages on demo.securityevaluators.com and attack pages on demo2.securityevaluators.com are both accessed through a free proxy service that operates as a mirror, the browser fails to enforce the same-origin policy, allowing the attack page at the bottom of the image to access session cookies for other domains.

The presence of the PHPSESSID cookie on the latter attack page shows that mirror services can undermine the same-origin policy; a real attack page would send this session cookie to the attacker, who could then impersonate the user in the web application. This example is not intended to pick on Hide.me — the same attack applies to any proxy service that follows this design. In fact, I found a public instance of PHProxy to be susceptible in the same way.

While the example shown here requires that a user actively authenticate through the proxy service, this is not necessarily a prerequisite for this type of attack to be security-relevant. For example, suppose a web forum application tracks a guest user’s read and unread posts using an anonymous-but-unique session tied to an HTTP cookie. Were this cookie to be exposed to a third party, that adversary could replay it to the server to view the victim’s post reading history.

A Same-Origin Policy Bypass

The consequences of a browser’s inability to distinguish pages’ origins when they are accessed through a mirror-based web proxy service are much more than the exposure of user history and even sessions. In fact, because browsers allow bidirectional AJAX requests between resources that fall under the same origin, when both an attack page and the site it targets are accessed through a mirror service that operates as shown in Figure 1, the attack page can interact with its target server in arbitrary ways.

Here is an example. Suppose a user accesses a web application on demo.securityevaluators.com that, among other things, allows the user to view and modify stored credit card information³. Then, the user accesses an attack page hosted on demo2.securityevaluators.com that attempts to retrieve the page containing the user’s credit card number through an AJAX request and display the retrieved source on the screen. As shown in Figure 6, the same-origin policy prevents this if both sites are accessed directly. In fact, the browser even logs a same-origin-related error to the browser console.

Figure 6. The same-origin policy, by default, blocks an attack site (bottom) from receiving the response to an AJAX request to a different origin (top).

Now suppose that instead of accessing the two sites directly, the affected user accesses them through the Hide.me proxy service, instead. Figure 7 shows that this time, the attack page can successfully send an AJAX request to the application and receive the user’s credit card number. Because this attack page is specifically written to allow Hide.me to rewrite the URL pointing to the credit card page, Hide.me rewrites it, causing the browser to treat the attack page and credit card page to belong to the same origin (proxy-nl.hide.me).