tl;dr Abstract

To improve performance, particularly for mobile users, many websites have started caching app logic on client devices via HTML5 local storage. Unfortunately, this can make common injection vulnerabilities even more dangerous, as malicious code can invisibly persist in the cache. Real-world examples of this problem have now been discovered in third-party “widgets” embedded across many websites, creating security risks for the companies using such services – even if their sites are otherwise protected against attacks. Striking a balance between security and performance can be difficult, but certain precautions may help prevent an attacker from exploiting local storage caches.

Background

Throughout the history of web development, people have found ways to use and abuse various technologies beyond their intended purposes. Before CSS gained widespread support, many developers created complex layouts with HTML tables. Now that browsers provide far more presentation-layer tools, one can recreate complex images using only CSS. Such tricks can at times be very helpful in overcoming the limits of a browser-based environment, but they can also inadvertently create security issues.
One feature commonly classified as part of HTML5 is local storage, a method for saving content on a visitor’s device that offers more space and flexibility than previous options (such as cookies). While intended as a client-side analogue to database storage, local storage has increasingly served another purpose: code caching. If a web app routinely requires large blocks of JavaScript, it can avoid downloading those chunks every time a visitor returns to the app by saving a copy of them in local storage. This can provide a significant performance boost, particularly on mobile devices, where bandwidth and typical caches can be much more limited.

Local Storage Attacks

However, this approach opens new possibilities for attacking the app. If the local storage can be compromised, an attacker could inject malicious code that persists in the client-side cache. This payload would then be executed by the web app each time a user opened the site – even if they’d previously closed the browser. In fact, eradicating such code can be quite difficult, and the victim website might not even be able to detect an ongoing attack. Artur Janc, a security engineer at Google, outlined these issues in a talk last December (video) detailing many of the dangerous ramifications they present, but as Janc notes, such an attack was also previously described by a paper from researchers at Berkeley (PDF) in May 2010.
Given the restrictions on access to a site’s local storage, modifying code saved there would nearly always require another vulnerability in the app as an initial attack vector. However, just one entry point for injecting code in a page would be enough to change the cache, and such problems tend to be quite common across the web. Many of these vulnerabilities (described as cross-site scripting, or XSS) are “reflected”, in that they only change a particular request for content, but using local storage automatically makes them capable of launching persistent attacks. Essentially, caching code in HTML5 local storage actually makes any existing cross-site scripting vulnerabities more dangerous.
And as influential researcher Michal Zalewski also noted a few months before Janc’s presentation, “if content from the compromised origin is commonly embedded on third-party pages (think syndicated ‘like’ buttons or advertisements), with some luck, attacker’s JavaScript may become practically invincible”. In this age of mash-ups, data from a variety of sources are often mixed together, creating implicit trust relationships that may have significant effects on the security of an app. When a developer includes third-party JavaScript on his or her site, that code has the same capabilities as any other script on the page. Of course, modifying a static file on a remote server is generally not possible, even if cross-site scripting issues are present. But what if a third-party script from a site with XSS problems also stored code in local storage?

Vulnerabilities in the Wild

As it turns out, this is no longer a hypothetical situation. Apture was a start-up that provided pop-up boxes for exploring content related to highlighted terms in a page. The service garnered praise from various tech media outlets, and the company was bought out by Google a few months ago. Just over a week ago, Google shut down the embedded search functionality, which was still in use by several sites after the acquisition. Apture is one example of a third-party “widget” service that used local storage code caching – and a page on the same domain as those scripts had a reflected XSS vulnerability which could be used to inject malicious code in the cache. This code would then be executed in the context of the site using Apture, meaning the problem with Apture’s service affected the security of many sites across the web.
And while Apture’s widgets are now offline, another service still operating on high-profile sites was recently found to have a similar issue (though in this case, scripts were not executed from the original site’s origin). This problem has been reported and is currently being addressed by the service’s engineers.

Reducing Risk

Ultimately, there isn’t a simple way of avoiding this type of vulnerability while still getting the performance gains of client-side code caching. Another new HTML feature, application cache, is actually geared towards precisely this use case and would be harder to compromise, but it can create UI warnings in some browsers, such as Firefox. (Such warnings are a good practice, but may be unwanted for third-party widgets.) Ideally, any data in local storage should be treated as untrusted, even if it’s just content instead of code. But if local storage is used for scripts, it should be accessed from a domain that only serves static files. This will help reduce the likelihood of an XSS vulnerability that would have direct access to local storage, though the overall structure of an app should be taken into account to prevent indirect access as well. Newer browsers also support features such as sandboxed inline frames and Content Security Policy that could help limit the impact of embedded widgets if they became compromised.
I think it’s important to note that many smart people, including those behind Apture, have used local storage for caching app logic – even Google and Bing use a similar technique on their mobile sites – and that in theory, this method should not make a website less secure. And for many web developers, it may not be immediately obvious why local storage data should not be trusted. This is another case where a clever trick that serves its primary goal very well has unintended consequences when considered in a broader context. It’s also an example of possibly making trade-offs which balance usability with risk. Understanding these conflicts and connections is part of what information security is all about – and what we do at Gemini every day. As browser features continue to expand and sites continue to integrate services from other domains, it’s likely we’ll see many more examples of security issues evolving in complexity – and organizations will need to adapt to such changes while still reducing risk.

Technical Details

For a site to use Apture widgets, the owner included a bit of JavaScript on their pages:
?
1
2
3
4
5
<script id="aptureScript">
(function (){var a=document.createElement("script");a.defer="true";
document.getElementsByTagName("head")[0].appendChild(a);})();
</script>
This dynamically loaded an external script hosted on apture.com with a site token specified. The external script included various parameters, such as title, logo, and search URLs that are associated with the account identified by the token. This code then loaded another script based on the user’s browser which actually began setting up the framework for Apture to integrate with the site’s content.
For browsers that support it, HTML5 cross-document messaging then came into play. The Apture script inserted an inline frame into the page that loaded a file from cdn.apture.com. A callback function allowed this iframe to pass messages back to the original window context where the script is running (the non-Apture site). This iframe then loaded the actual app logic and passed the code back to the original site via the cross-document messaging interface.
At this point, you’re probably wondering why Apture didn’t simply load the app logic as another script in the original page; in fact, that’s precisely what Apture did if the browser didn’t support newer HTML5 features. But Apture’s iframe setup allowed them to take advantage of another HTML5 innovation that made their service load much faster. Web storage functionality provides the localStorage object, a place to save key/value data on the client which allows for more space and flexibility than cookies. This storage is persistent across browser sessions, but is specific to each domain and access to it is restricted by a same-origin policy.
Apture used a localStorage object for cdn.apture.com not only to save data, such as an ID for tracking users, but to actually cache their app logic code. If the cdn.apture.com iframe detected that this cache already existed, it would simply load the code from localStorage rather than issue another HTTP request for the 272KB worth of JavaScript – saving time and bandwidth. Apture introduced this functionality in January 2011.
But how does one load code from localStorage? For Apture, with this line in the cross-domain callback function:
?
1
window.execScript ? window.execScript(f) : window.eval(f);
Seeing code such as this should immediately raise red flags in the mind of any web developer. Those familiar with browser security may have heard the adage that “eval is evil”, and it certainly applies here. The eval function (or the analogous execScript function also seen above) treats its input as valid JavaScript and simply executes it in the current window’s global context. If an attacker can send malicious code to the function, that code will also be executed – a class of vulnerabilities known as cross-site scripting (XSS).
In Apture’s case, though, the code came from the cdn.apture.com storage, so one might assume it can be trusted – in theory, only pages from cdn.apture.com can modify the localStorage cache. But once again, the power of cross-site scripting demonstrates that many seemingly trustworthy data sources are still potential avenues of attack. The presence of any XSS on a cdn.apture.com page, including reflected XSS, would allow an attacker to execute code in that domain’s context and thus modify the localStorage object.
As it turns out, Apture did have an exploitable XSS vulnerability. The cdn.apture.com domain actually mirrored www.apture.com, including a topic page that loaded a topic title from the URL path and a YouTube video ID from a GET parameter. Both of these values were included in the page without being escaped to prevent XSS. This example URL includes a script that appends “alert(document.cookie)” to the app logic in localStorage:
http://cdn.apture.com/search/xss?yt=%22%3E%3Cscript%3Eif%28
window.x%21%3D1%29%7BlocalStorage%5B%27app-49971756%27%5D
%3DlocalStorage%5B%27app-49971756%27%5D%2b%22alert%28
document.cookie%29%3B%22%7Dwindow.x%3D1%3C%2fscript%3E
The window.x logic ensures that the code only executes once, since the parameter appears in the topic page multiple times. In an actual attack, more code would likely be needed, as the specific localStorage key includes a version number that could change depending on the user. This does not stop the attack, however, as the correct version can be loaded by the script before making changes to localStorage.
Once this vulnerability is used to insert attack code into localStorage (e.g. if the above URL were loaded in an invisible iframe on an attacker’s site), visiting any site that had Apture’s widgets would cause the attack code to be loaded from the Apture iframe and executed in the context of the non-Apture site. And since this is essentially an example of DOM-based XSS (the code is loaded dynamically on the client side), requests sent to those sites’ servers would not include any XSS fingerprints, such as <script> in a GET or POST parameter. In summary, the localStorage code caching turned one reflected XSS vulnerability on Apture’s site into persistent, client-side XSS across all domains using their service.

Post a Comment

 
Top
Google+