Web Storage Security

The web waits for no one, not even W3C.

While the HTML5 specification isn’t finalized, and HTML5 Storage has even been broken out into its own Web Storage Specification, which is even further from being finalized, code continues to move to the client and more developers are (mis-) using the next generation features that are already available in the browsers. Engineers and researchers in the WhiteHat Security Threat Research Center are in a unique position to know “where there is code, there are vulnerabilities,” and JavaScript is certainly no exception.

Over the past few months, the Threat Research Center has implemented new checks into WhiteHat Sentinel to better identify and analyze the usage of Web Storage and its potential security impact.  During the course of this research, I analyzed over 600 applications that made at least one call to Web Storage — “getItem” or “setItem”.  The preliminary results may surprise you. They sure surprised me.

Before I jump into the vulnerability discussion, a brief word about some of the so-called “security advice” concerning HTML5 APIs and specifically Web Storage. I’ll be the first to admit that before this project I’d never used the Web Storage APIs; this was a from-scratch effort. Like any good developer (or hacker) learning a new technology, I googled “Web Storage Security”, “localStorage Security”, and “HTML5 Storage Security”. The results were somewhat discouraging.  I couldn’t find a single vulnerable code example and most security commentaries boiled down to either “there is no major risk” or “if developers use Web Storage properly there is no risk.”

The argument is that because stored values aren’t transmitted over HTTP we actually have a more secure option for storing data that is only needed on the client. In my opinion, this is just flat out wrong.  Arguing that “if developers use it correctly there is no risk” is like saying “if PHP developers use $_GET correctly there won’t be any problems.” We all know how that turns out.

So I knew I needed to go to the source. Section 7 of the Web Storage Specification is titled “Security”; certainly we can get some good advice here. Honestly though, I found section 7.1’s warning about DNS spoofing attacks and 7.2’s warning about cross-directory attacks to be a bit hollow. I’m not saying these attacks don’t exist, but certainly we can give some better advice than “Use TLS” and “Don’t implement on shared domains”. Section 7.3, on implementation risks, appears to be entirely targeted at browser makers. If developers can’t go to W3C for advice on how and how not to use Web Storage securely, then where can they go? It looks like we are back to Stack Overflow and random blog posts. With that being the state of security advice on Web Storage, I figured I’d throw my hat in the ring.

Examples of Vulnerability:

Evil Roommate / Public Computer

Firefox’s about:home page is vulnerable to DOM (document object model) XSS via localStorage injection through the snippets functionality. While I can’t send you a link or build a malicious website to exploit this issue I’m willing to bet that thousands of people use FireFox on a shared or public computer every day. It sure would be nice to be able to log all of those keystrokes even after the browser is closed and private data has been cleared.

Screen Shot 2013-05-18 at 5.12.34 AM

Just sit down and run a weaponized version of the following bookmarklet:

javascript:window.localStorage.setItem(‘snippets’,'<iframe src=”https://www.whitehatsec.com” onload=”prompt()” style=”width:100%;height:100%;z-index:9999999;position:absolute;left:0px;top:0px;”/>’);

When contacted about the above issue via email the Mozilla security team advised that they are migrating the functionality off of localStorage for reasons other than security. Even so, I’ll be keeping my Firefox usage to my own computer that is always locked whenever I am not using it. At least until this functionality is patched.

DOMXSS –> localStorage XSS –> The persistent vector your sever will never see.

Vulnerable Code:

<script language=”JavaScript”>

var Id = getPramValue(“id”);

var persistId = localStorage.getItem(‘id’);

if( isValid(Id) ){

document.write(‘<a href=”http://www.example.com/?s=13436&id=’+ Id” id=”store_locator”>’);

document.write(‘<div>Find Store</div>’);

document.write(‘</a>’);

} else if( localStorage && isValid(persistId)) {

document.write(‘<a href=”http://www.example.com/?s=13436&id=’+ persistId” id=”store_locator”>’);

document.write(‘<div>Find Store</div>’);

document.write(‘</a>’);

}else {

document.write(‘<a href=”http://www.example.com/locator” class=”scroll linktomap” id=”store_locator”>’);

document.write(‘<div>Find Store</div>’);

document.write(‘</a>’);

}

</script>

Proof of Concept:

<a href=”http://www.example.com/#?id=’”><img/src=”x”onerror=eval(String.fromCharCode(119,105,110,100,111,119,46,108,111,99,97,108,83,116,111,114,97,103,101,46,115,101,116,73,116,101,109,40,39,105,100,39,44,39,34,62,60,105,109,103,47,115,114,99,61,92,34,120,92,34,111,110,101,114,114,111,114,61,97,108,101,114,116,40,49,41,62,39,41))>

The String.fromCharCode here just makes it easier to insert the needed injection into localStorage without excessive quote escaping. Here is what it decodes to:

window.localStorage.setItem(‘id’,'”><img/src=\”x\”onerror=alert(1)>’)

The Always and Never of Web Storage

ALWAYS:

Always  validate, encode, and escape user input before placing into localStorage or sessionStorage

Always  validate, encode, and escape data read from localStorage or sessionStorage before writing onto the page (DOM).

Always  treat all data read from localStorage or sessionStorage as untrusted user input.

NEVER:

Never store sensitive data using Web Storage: Web Storage is not secure storage. It is not “more secure” than cookies because it isn’t transmitted over the wire. It is not encrypted. There is no Secure or HTTP only flag so this is not a place to keep session or other security tokens.

Never use Web Storage data for access control decisions or trust the serialized objects you store here for other critical business logic. A malicious user is free to modify their localStorage and sessionStorage values at any time, treat all Web Storage data as untrusted.

Never write stored data to the page (DOM) with a vulnerable JavaScript or library sink.  Here is the best list of JavaScript sinks that I am aware of on the web right now.  While it is true that a perfect storm of tainted data flow must exist for a remote exploit that relies 100% on Web Storage you must consider two alternate scenarios. First, consider the evil roommate, unlocked, unattended, or public computer scenario in which a malicious user has temporary physical access to your user’s web browser. The computer’s owner may have disallowed a low privileged user from installing malicious add-on but I’ve never seen a user prevented from making a bookmark. Second, don’t ignore the possibility of improper Web Storage usage allowing escalation of another vulnerability such as reflective cross-site scripting into persistent cross-site scripting.