WSTG - Stable

Testing Browser Storage

ID
WSTG-CLNT-12

Summary

Browsers provide the following client-side storage mechanisms for developers to store and retrieve data:

  • Local Storage
  • Session Storage
  • IndexedDB
  • Web SQL (Deprecated)
  • Cookies

These storage mechanisms can be viewed and edited using the browser’s developer tools, such as Google Chrome DevTools or Firefox’s Storage Inspector.

Note: While cache is also a form of storage it is covered in a separate section covering its own peculiarities and concerns.

Test Objectives

  • Determine whether the website is storing sensitive data in client-side storage.
  • The code handling of the storage objects should be examined for possibilities of injection attacks, such as utilizing unvalidated input or vulnerable libraries.

How to Test

Local Storage

window.localStorage is a global property that implements the Web Storage API and provides persistent key-value storage in the browser.

Both the keys and values can only be strings, so any non-string values must be converted to strings first before storing them, usually done via JSON.stringify.

Entries to localStorage persist even when the browser window closes, with the exception of windows in Private/Incognito mode.

The maximum storage capacity of localStorage varies between browsers.

List All Key-Value Entries

for (let i = 0; i < localStorage.length; i++) {
  const key = localStorage.key(i);
  const value = localStorage.getItem(key);
  console.log(`${key}: ${value}`);
}

Session Storage

window.sessionStorage is a global property that implements the Web Storage API and provides ephemeral key-value storage in the browser.

Both the keys and values can only be strings, so any non-string values must be converted to strings first before storing them, usually done via JSON.stringify.

Entries to sessionStorage are ephemeral because they are cleared when the browser tab/window is closed.

The maximum storage capacity of sessionStorage varies between browsers.

List All Key-Value Entries

for (let i = 0; i < sessionStorage.length; i++) {
  const key = sessionStorage.key(i);
  const value = sessionStorage.getItem(key);
  console.log(`${key}: ${value}`);
}

IndexedDB

IndexedDB is a transactional, object-oriented database intended for structured data. An IndexedDB database can have multiple object stores and each object store can have multiple objects.

In contrast to Local Storage and Session Storage, IndexedDB can store more than just strings. Any objects supported by the structured clone algorithm can be stored in IndexedDB.

An example of a complex JavaScript object that can be stored in IndexedDB, but not in Local/Session Storage are CryptoKeys.

W3C recommendation on Web Crypto API recommends that CryptoKeys that need to be persisted in the browser, to be stored in IndexedDB. When testing a web page, look for any CryptoKeys in IndexedDB and check if they are set as extractable: true when they should have been set to extractable: false (i.e. ensure the underlying private key material is never exposed during cryptographic operations.)

const dumpIndexedDB = dbName => {
  const DB_VERSION = 1;
  const req = indexedDB.open(dbName, DB_VERSION);
  req.onsuccess = function() {
    const db = req.result;
    const objectStoreNames = db.objectStoreNames || [];

    console.log(`[*] Database: ${dbName}`);

    Array.from(objectStoreNames).forEach(storeName => {
      const txn = db.transaction(storeName, 'readonly');
      const objectStore = txn.objectStore(storeName);

      console.log(`\t[+] ObjectStore: ${storeName}`);

      // Print all entries in objectStore with name `storeName`
      objectStore.getAll().onsuccess = event => {
        const items = event.target.result || [];
        items.forEach(item => console.log(`\t\t[-] `, item));
      };
    });
  };
};

indexedDB.databases().then(dbs => dbs.forEach(db => dumpIndexedDB(db.name)));

Web SQL

Web SQL is deprecated since November 18, 2010 and it’s recommended that web developers do not use it.

Cookies

Cookies are a key-value storage mechanism that is primarily used for session management but web developers can still use it to store arbitrary string data.

Cookies are covered extensively in the testing for Cookies attributes scenario.

List All Cookies

console.log(window.document.cookie);

Global Window Object

Sometimes web developers initialize and maintain global state that is available only during the runtime life of the page by assigning custom attributes to the global window object. For example:

window.MY_STATE = {
  counter: 0,
  flag: false,
};

Any data attached on the window object will be lost when the page is refreshed or closed.

List All Entries on the Window Object

(() => {
  // create an iframe and append to body to load a clean window object
  const iframe = document.createElement('iframe');
  iframe.style.display = 'none';
  document.body.appendChild(iframe);

  // get the current list of properties on window
  const currentWindow = Object.getOwnPropertyNames(window);

  // filter the list against the properties that exist in the clean window
  const results = currentWindow.filter(
    prop => !iframe.contentWindow.hasOwnProperty(prop)
  );

  // remove iframe
  document.body.removeChild(iframe);

  // log key-value entries that are different
  results.forEach(key => console.log(`${key}: ${window[key]}`));
})();

(Modified version of this snippet)

Attack Chain

Following the identification any of the above attack vectors, an attack chain can be formed with different types of client-side attacks, such as DOM based XSS attacks.

Remediation

Applications should be storing sensitive data on the server-side, and not on the client-side, in a secured manner following best practices.

References

For more OWASP resources on the HTML5 Web Storage API, see the Session Management Cheat Sheet.