Monday, July 12, 2010

HTML5, Local Storage, and XSS

A nice new feature of HTML 5 is local storage. Briefly, this is a client side storage option that can be easily accessed via JavaScript. The benefit of local storage over other client side storage options is that local storage allows more storage space than other options (cookies, flash obj, etc). In addition, unlike cookies, the data is not automatically appended to every request by the browser. This is a nice benefit for those attempting to minimize data transmission between the client and server.

However, there are a few security considerations that should be evaluated before completely jumping on board with local storage. 

XSS and Local Storage

A popular target of XSS attacks is the session identifier and possibly any sensitive data stored client side. Just like session IDs stored within cookies, a session id within local storage can be easily stolen by the attacker.
Example XSS to steal session ID from cookie
<script>document.write("<img src='"+document.cookie+"'>");</script>

Example XSS to steal session ID from local storage
<script>document.write("<img src='"+localStorage.getItem('foo')+"'>");

The syntax is easy, just access localStorage using "getItem" and reference the variable name holding the data. The only real difference here is the attacker would need to inspect the client side JavaScript to pick out the correct variable names to use.

HTTPOnly and Local Storage

Another problem with using local storage for session ids is the inability to apply the HTTPOnly flag that we use with cookies. The HTTPOnly flag instructs browsers to not allow JavaScript access to the cookies. This is a great additional layer of defense to prevent an XSS attack from stealing the user's session (of course lots of other damage is still possible via XSS).  Since local storage is intended to be accessed via JavaScript the idea of HTTPOnly is not compatible with this design. 

Notes for penetration testing:
Proof of concept XSS with local storage:

Get a Local Storage Value via URL scriptlet

Set a Local Storage Value via URL scriptlet:

Set a Local Storage Value with JSON via URL scriptlet:
javascript:localStorage.setItem('fooName', JSON.stringify('data1:a,"data2":b,data3:c'));

Get Number of Local Storage Objects via URL scriptlet:

Clearing all Local Storage associated with site:
Final Thoughts on Local Storage and Security
1. Don't use local storage for session identifiers. Stick with cookies and use the HTTPOnly and Secure flags.
2. If cookies won't work for some reason, then use session storage which will be cleared when the user closes the browser window.
3. Be cautious with storing sensitive data in local storage. Just like any other client side storage options this data can be viewed and modified by the user.

-Michael Coates


  1. I'm confused by your post. The XSS examples seem to give the message that localStorage is completely insecure to web apps. Any web app can read any localStorage data. Is it true?

  2. Handy article. Any persistent client-side information always opens the floodgates for programmers who don't quite know the difference between client and server...

    "The only real difference here is the attacker would need to inspect the client side JavaScript to pick out the correct variable names to use."

    From a quick look at the spec there doesn't seem to be anything preventing you from enumerating the values.

  3. I doubt that we are going to see session identifiers stored in local storage any time soon - with the authorization usually being on server side this is impractical. Local storage is certainly not a replacement for cookies, it is an additional tool available to web developers.

    But - sure, an XSS vulnerability will immediately compromise all the local storage data. Given that most website fail to protect against XSS, storing sensitive data in local storage is something to be thought about carefully. Not that HTTPOnly helps much - even if the session identifier cannot be retrieved, with an authorized user and no same-origin policy in the way it is usually possible to automate the webpage to do anything the attacker might want.

    @johnjbarton: Local storage is only readable from the domain that wrote the data. But if you found a cross-site scripting vulnerability in a website you can run code in the domain of that website.

  4. "Another problem with using local storage for session ids is the inability to apply the HTTPOnly flag that we use with cookies."

    I tried to argue in the HTTPOnly bug that this solution is *not* ideal, and will become less and less ideal as new client-side technologies are invented. Browsers need to implement in-page sandboxing with randomized boundaries. Something like:

    <sandbox-start boundary="fa20bf0c09ff">
    ...untrusted content...
    <sandbox-end boundary="fa20bf0c09ff">

    Even if all browsers implement this today, this won't be useful for 5-10 years, but it needs to be implemented at some point(!) otherwise we're going to continue to suffer through easily solvable XSS problems forever. (The other half of the XSS story is XHR-style access controls on 3rd party cookies or, in fact, 3rd party information/connections of any kind.)

  5. @johnjbarton - The same origin policy applies. So data set into local storage by domain A could not be accessed by domain B.

    @rushyo - Agreed, the spec doesn't restrict enumerating values. I just haven't seen any code or support yet in the implementations within browsers. Its probably out there, or will be soon.

    @Wladimir Palant - Storing session IDs in local storage would be an alternative to using cookies. The authorization would still be handled server side, this would just be a change to the method of storing the session identifier on the client.

    @voracity - I've seen that idea before and think it would be very powerful. For whatever reason it hasn't taken off yet.

  6. Agreed that httponly can't be appplied..But wondering how xss attack can be done and retrieve the localstorage data set by other domains? Thank you for your response in advance


Note: Only a member of this blog may post a comment.