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.)
Print All the Contents of IndexedDB
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.