Into The Belly Of The Beast

Browser Session and Cookie Extraction Over WebSocket With Chrome DevTools Protocol

In this post, we introduce a post-exploitation concept and tool designed to dump all cookies, session, and local storage entries to JSON files and it does this through a localhost WebSocket connection using Chrome/Brave/Edge's remote debug functionality (Chrome DevTools Protocol).

The Belly - Chrome DevTools Protocol (CDP)

CDP was created to allow developers to interact with and control Chromium-based browsers (Edge/Brave/Chrome/Opera, etc.) programmatically and enables things like inspecting web pages in real-time, debugging JavaScript, DOM manipulation, monitoring network activity (extremely useful), and performing nearly anything possible within DevTools. It essentially enables a communication channel between the browser and a client (e.g., a script or some other external application) via a WebSocket through a "debugging port".

When the --remote-debugging-port switch is enabled (supplied via the command line when launching the browser), external tools or scripts can connect to the browser over the network. It's typically used for tasks such as testing automation with tools like Playwright, Puppeteer, Selenium, etc., or just to debug web applications remotely.

For example, running chrome.exe --remote-debugging-port=9481 starts Chrome with debugging enabled on port 9481, and is accessible via http://localhost:9481 or via the CDP WebSocket (ws://localhost:9481)

You can learn more about CDP here.

The Concept

1. Find the users' default browser

This is usually done by querying the HKCU:\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\https\UserChoice registry path to determine which default program is associated with opening URLs. In this case, the value for ProgId gives us a hint as to what the default browser is. The ProgId is typically something like ChromeHTML, or BraveHTML, etc, but in the odd case where the default browser hasn't been set, it might look more like FirefoxURL-[RandomString]. Either way, we can usually determine the default browser by simply retrieving that ProgId value.

2. Relaunch the browser with the --remote-debugging-port enabled

If the browser is already open and does not have remote debugging enabled (if it does already, something is unusual), we need to terminate it and relaunch it in order to enable the debug port. Unfortunately, the main caveat to this tool is that the debug port cannot just be enabled on an already running browser (that I know of). This may or may not spark some suspicion or curiosity from the end-user's perspective.

3. Dump all the things

Once the browser is relaunched with the --remote-debugging-port switch enabled, we can interact with it freely, and have access to a number of things that are useful during engagements, particularly session information (auth tokens, cookies, etc.).

PS C:\> .\TokenTaker.ps1
[+] Targeting default browser: brave at C:\Program Files\BraveSoftware\Brave-Browser\Application\brave.exe
[+] Default profile directory: C:\Users\user\AppData\Local\BraveSoftware\Brave-Browser\User Data
[+] Starting brave with default profile and debugging on port 9481...
[+] Found 2 tabs.
[+] Connecting to tab: chrome://newtab/ via ws://127.0.0.1:9481/devtools/page/14C19B86195AE6F889CCF53BBFCED2F6
[+] WebSocket connected.
[+] Dumping it all...
[+] WebSocket closed.
[+] Found token for .login.microsoftonline.com
- Total Cookies: 862
- Total Local storage items: 0
- Total Session storage items: 0
[+] Dumped files saved to C:\Users\user\AppData\Local\Temp\901b7af1-32c7-47c8-a5c0-9f875d0855e1

TokenTaker

TokenTaker is a proof-of-concept PowerShell script that automates the above process and dumps all cookies, session and local storage. It can be downloaded from our GitHub page here.

The script will first identify the default browser by querying the Windows registry for the ProgId of the application handling HTTPS links and uses a lookup table ($browserMap) to match ProgId values to known browser paths and user profile directories. Once it’s figured out which browser to use, it first checks if remote debugging is already enabled (it really shouldn’t be by default), but if it is, it will use that. If an existing browser session is detected without remote debugging (the more likely scenario), it is forcibly terminated and a new browser instance is started with --remote-debugging-port=9481.

Once it has confirmed that remote debugging is enabled, it will retrieve the list of open tabs from the debugging API, and extracts the WebSocket debugging URL from the /json endpoint. The /json endpoint returns a list of active tabs, each with a webSocketDebuggerUrl, which is used to send and receive debugging commands.

Example response from http://127.0.0.1:9481/json

After it’s retrieved list of active tabs, it connects to the first one it finds using the webSocketDebuggerUrl:

Retrieving list of active tabs and initial WebSocket Connection

Extracting Cookies via the Network.getAllCookies Method

The first command sent over the WebSocket is:

$cookieCommand = @{ 
    "id" = 1;
    "method" = "Network.getAllCookies"
} | ConvertTo-Json

The above command tells the browser to use the Network.getAllCookies method, which fetches all cookies accessible to the browser, along with assigning a request ID to track responses, and converted to JSON format.

The script then sends this JSON-encoded message over the WebSocket with the Send-WsMsg function:

Send-WsMsg -WebSocket $ws -Message $cookieCommand -OutputFile $cookieFile | Out-Null

Inside Send-WsMsg, the command is converted to UTF-8 bytes and sent to the browser WebSocket, and the WebSocket returns a JSON object with all of the stored cookies:

Example cookie output

Extracting Local Storage via the Runtime.evaluate Method

Unlike cookies, Local Storage and Session Storage are not accessible via Network methods, and instead, JavaScript execution is required via the Runtime.evaluate method. To retrieve Local Storage, the script sends:

$localStorageCommand = @{ 
    "id" = 2;
    "method" = "Runtime.evaluate"; 
    "params" = @{ "expression" = "JSON.stringify(localStorage)" } 
} | ConvertTo-Json

The Runtime.evaluate method runs JavaScript inside the active browser tab, and "params": { "expression": "JSON.stringify(localStorage)" } is used to convert the Local Storage into a JSON string. The $localStorageCommand is then sent to the WebSocket, and it returns something like the following:

Example Local Storage Output

Session Storage works the same way as Local Storage but is tab-specific and cleared when the browser is closed.

After retrieving cookies, local storage, and session storage, TokenTaker gracefully terminates the WebSocket connection. This sends a Close frame over the WebSocket, ensuring a clean disconnection.

$ws.CloseAsync(
    [System.Net.WebSockets.WebSocketCloseStatus]::NormalClosure, 
    "Done", 
    [System.Threading.CancellationToken]::None
).Wait()
Write-Output "[+] WebSocket closed."

All files are saved to a random GUID-named folder in $env:TEMP.

For Defenders

Disabling the --remote-debugging-port functionality can be accomplished in several ways, including GPOs, registry modifications, and system-level mitigations.

Since Chrome, Brave and Microsoft Edge are built on Chromium, the following similar methods can be used.

Completely Disable Developer Tools via Group Policy

You can disable Developer Tools entirely, which will also block remote debugging:

  1. Download the Chrome/Edge ADMX templates from:

  2. Import the ADMX files into Group Policy Editor.

  3. Navigate to:

    Computer Configuration -> Administrative Templates -> Google Chrome / Microsoft Edge
  4. Enable:

    • "Disable Developer Tools" (DeveloperToolsAvailability set to 2)

Block Just Remote Debugging Ports via Group Policy

  1. In Group Policy Editor, go to:

    Computer Configuration -> Administrative Templates -> Google Chrome / Microsoft Edge -> Security Settings
    
  2. Enable the Command-line argument overrides are not allowed policy:(RestrictCommandLineFlags).

  3. Add the following blocked arguments:

    --remote-debugging-port
    

Modify The Windows Registry (an alternative)

If GPO is not an option, use the Windows Registry:

  1. Open Registry Editor (regedit).

  2. Navigate to (or create):

    For Chrome

    HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome
    

    Or for Edge:

    HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\MicrosoftEdge
    
  3. Create a DWORD (32-bit) Value:

    • Name: DeveloperToolsAvailability

    • Value: 2 (Disabled)

  4. Create another key (if it doesn’t exist):

    HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome\CommandLineFlagSecurity
    
    • Add a String Value: RestrictCommandLineFlags

    • Set value to: --remote-debugging-port

Network-Based Restrictions

If remote debugging is being misused over the network, consider blocking relevant TCP ports.

  • Remote debugging typically runs on port 9222 (the default) or any other arbitrary port.

  • Use a firewall rule (Windows Defender Firewall, Group Policy, or network firewall) to block outbound/inbound traffic on TCP 9222, etc.

Application Whitelisting

  • Use Windows AppLocker or Microsoft Defender Application Control to restrict the use of Chrome/Edge with non-approved command-line arguments.

Disabling Debugging Features via Browser Extensions

  • Some security tools offer browser extensions that block debugging. Consider deploying a security extension that prevents script injection or debugging.

Disclaimer

This tool is provided purely for educational purposes and authorized security testing only. It is intended to help users understand web browser security concepts, vulnerabilities, and to help find ways to defend against these sorts of things. Unauthorized use, including but not limited to attacking systems without explicit permission, is strictly prohibited and may violate applicable laws. The developers are not responsible for any misuse, damage, or legal consequences resulting from the use of this tool. Use responsibly and ethically, and always obtain proper consent before testing any system.