Post

[Barbhack 2026] - Weaponizing Browser Extensions: Injection, GPO Bypass, and Ghost Persistence

[Barbhack 2026] - Weaponizing Browser Extensions: Injection, GPO Bypass, and Ghost Persistence

Yesterday, I had the pleasure of presenting at Barbhack 2026 an extended and in-depth version of my research on browser extensions. Where the short Beerump format got me to focus solely on the persistence primitive (GAP), this time I was able to unfold the entire attack chain: from initial C2 agent injection, through enterprise GPO bypasses, all the way to fileless persistence without any artifacts left on disk.

The talk was titled Weaponizing Browser Extensions - C2 Agent Injection and Fileless Persistence in Chromium and aimed to demonstrate that the browser has become a prime playground for post-exploitation, often overlooked by EDRs.

Why target the browser?

Today, the browser is no longer a simple web page viewer—it is the new workstation. M365, SharePoint, Teams, GitHub, OneDrive: all sensitive data and active sessions (cookies, MFA tokens, credentials) flow through it.

With EDRs becoming increasingly effective at detecting traditional system agents, browser extensions offer several competitive advantages:

  • Legitimate process: the browser is signed and whitelisted.
  • No intrusive memory injection: we stay within the browser’s sandbox (or exit it cleanly via Native Messaging).
  • Cross-platform: a single codebase works on Windows, Mac, and Linux.
  • Natural persistence: the extension restarts automatically with the browser.

The 3 injection vectors (and their limits)

During my presentation, I reviewed the different methods for injecting an extension into a Chromium browser (Edge, Chrome, Brave, Vivaldi):

  1. From the official store: not very discreet and subject to validation.
  2. Via Group Policies (GPOs): perfect for silent deployment, but requires admin privileges.
  3. Via developer mode: loading an unpacked extension (requires a GUI) or loading a self-signed .crx file.

The main issue arises in enterprise environments: security GPOs block unauthorized extensions using allowlists (ExtensionInstallAllowlist) and blocklists (ExtensionInstallBlocklist).

Bypassing GPOs: The Secure Preferences file

I detailed the technique of modifying the user’s Secure Preferences file (located in %LOCALAPPDATA%\Microsoft\Edge\User Data\Default\). This file contains the state of installed extensions and is protected by HMACs and a super_mac computed from the user’s SID (Windows) or Hardware UUID (macOS).

By recalculating these integrity values, we can force our extension to be accepted, even if it is blocked by existing GPOs.

The ID Spoofing flaw

However, if a GPO blocks any ID not present in the allowlist, injecting just any extension won’t work. We must spoof the ID of a legitimate extension available on the store.

An extension’s ID is computed as follows: sha256(public_key) → first 32 characters → convert each hex character to a letter (0 → a, …, f → p).
I showed how to retrieve the public key of a legitimate extension (e.g., Google Drive) and inject it into our malicious agent’s manifest.json. The browser then generates the same ID as the legitimate extension, making the allowlist bypass completely transparent.

The tools from the demo

To automate all of this, I presented two tools developed specifically for this research:

  • stomp (Python): retrieves the public key from the store, modifies the manifest, recalculates the MACs for Secure Preferences, and packages everything into a .zip archive with an injection script.
  • BELoader (C, ~100 KB): a standalone binary that embeds the extension. It performs the same operations stealthily, using obfuscation and dynamic API resolution for better OPSEC.

Breaking out of the sandbox: RCE via Native Messaging

Once the extension is injected, how do we execute system commands? I detailed the use of the chrome.sendNativeMessage API. This officially allows an extension to communicate with a native binary on disk, provided that binary is registered via a JSON manifest placed in a specific path (Windows registry or NativeMessagingHosts folder).

This allows us to escape the browser’s sandbox and execute arbitrary code on the target machine, paving the way for a full RCE module integrated into a C2 framework.

The highlight: GAP (Ghost Anchor Persistence)

Finally, I concluded with the major discovery: GAP, a fileless persistence technique within Chromium browsers.

When the extension’s Service Worker loads for the first time, Chromium compiles it and stores it in a LevelDB cache (ScriptCache), indexed by the script’s URL (e.g., chrome-extension://<ID>/background.js) and not by its content.

On browser restart, this cache is served directly if the extension’s folder exists, without any verification that the file on disk matches the cached version.

By exploiting this:

  1. I inject the malicious extension (Folder A).
  2. I trigger the Service Worker registration (the browser compiles it and writes it to the cache).
  3. I replace the malicious folder with a benign folder (Folder B) that has the same ID and same filenames, but an empty background.js.
  4. I delete the malicious folder.

Result: no malicious artifacts remain on disk, yet the malicious code continues to execute indefinitely from the browser’s cache. Zero files, total persistence. This technique has been validated on Edge, Chrome, Brave, and Vivaldi.

The slides

If the PDF doesn’t display correctly, you can open the slides directly.

Going further

For the full technical breakdown of the GAP persistence technique, check out the dedicated article: GAP - Ghost Anchor Persistence: Fileless Extension Persistence in Chromium Browsers.

The tools and sources presented during the talk are available on my GitHub:

A huge thank you to the Barbhack team for the invitation. See you next time!

This post is licensed under CC BY 4.0 by the author.