EXTENSIONS
FORCELIST
UNPACKED
MANIFEST.JSON
DetectionBlue TeamSOCOctober 12, 2026•9 min read

Detecting Malicious Browser Extension Persistence

A browser extension runs inside the one application every user has open all day, with access to cookies, pages and sessions. It also survives reboots by design.

Scroll

Malicious extensions give an attacker a persistent foothold inside the browser: they read pages, steal session cookies and tokens, inject content and can talk to a native host on the machine. Attackers install them through policy keys, by loading unpacked extensions with a command-line flag, or by editing profile files directly. Because extensions sync across devices and are written to disk in predictable locations, they can be inventoried and monitored well.

Signals at a Glance

SignalWhereATT&CK
ExtensionInstallForcelist or ExtensionSettings policy written under Policies\Google\Chrome or Policies\Microsoft\EdgeSysmon 13, registry auditT1176
Browser launched with --load-extension or --disable-extensions-exceptSysmon 1, command-line logsT1176
Unpacked extension: manifest with no update_url, loaded from outside the Extensions store pathProfile file inventoryT1176
Risky permissions: nativeMessaging, debugger, webRequest, cookies, management, all-URL host accessmanifest.json analysisT1176
Profile Preferences or Secure Preferences modified by a non-browser processFile events, EDRT1112
New extension ID not in your approved list, or developer mode enabled on managed endpointsPolicy compliance, inventoryT1176

Install Paths and Policy Writes

Three install routes are worth monitoring: a policy-based force install (needs admin), a launch flag that loads an unpacked directory (works for a standard user and persists via a modified shortcut or task), and direct edits to the profile Preferences. Cover the first two with registry and process telemetry:

// KQL: browser extension install routes
let policyWrites = DeviceRegistryEvents
    | where ActionType == "RegistryValueSet"
    | where RegistryKey has_any (@"\Policies\Google\Chrome\ExtensionInstallForcelist",
                                 @"\Policies\Microsoft\Edge\ExtensionInstallForcelist",
                                 @"\Policies\Google\Chrome\ExtensionSettings",
                                 @"\Policies\Microsoft\Edge\ExtensionSettings")
    | project Timestamp, DeviceName, Route = "policy", Detail = strcat(RegistryKey, " = ", RegistryValueData),
              InitiatingProcessFileName, InitiatingProcessCommandLine;
let launchFlags = DeviceProcessEvents
    | where FileName in~ ("chrome.exe", "msedge.exe", "brave.exe")
    | where ProcessCommandLine has_any ("--load-extension", "--disable-extensions-except")
    | project Timestamp, DeviceName, Route = "load-extension", Detail = ProcessCommandLine,
              InitiatingProcessFileName, InitiatingProcessCommandLine;
union policyWrites, launchFlags
| order by Timestamp desc
// tune/validate: IT-managed force-install policy changes should come from your management tooling only.

Direct edits to a profile’s Secure Preferences file are a quieter route, as is installing an extension on a profile before the browser first runs. Monitor writes to Preferences and Secure Preferences by any process other than the browser itself.

Inventory and Permission Review

On disk, each extension unpacks to Extensions\<id>\<version>\manifest.json within the browser profile. Store-installed extensions carry an update_url pointing at the vendor store, so a manifest without one is either unpacked or sideloaded. This sweep reads every Chrome and Edge manifest on a host and highlights risky permissions:

# PowerShell: inventory Chrome and Edge extensions and flag risky manifests
$risky = 'nativeMessaging','debugger','webRequest','webRequestBlocking','cookies','management','proxy','tabs'
$paths = 'C:\Users\*\AppData\Local\Google\Chrome\User Data\*\Extensions\*\*\manifest.json',
         'C:\Users\*\AppData\Local\Microsoft\Edge\User Data\*\Extensions\*\*\manifest.json'
Get-ChildItem -Path $paths -ErrorAction SilentlyContinue | ForEach-Object {
  $m = Get-Content $_.FullName -Raw | ConvertFrom-Json
  $perms = @($m.permissions) + @($m.host_permissions) | Where-Object { $_ }
  $hit = $perms | Where-Object { $risky -contains $_ -or $_ -eq '<all_urls>' -or $_ -like '*://*/*' }
  [pscustomobject]@{
    Extension  = $_.Directory.Parent.Name
    Name       = $m.name
    Version    = $m.version
    UpdateUrl  = $m.update_url
    RiskyPerms = ($hit -join ',')
    Path       = $_.FullName
  }
} | Where-Object { -not $_.UpdateUrl -or $_.RiskyPerms } | Sort-Object Extension
# tune/validate: localized names show as __MSG_*__; resolve them from _locales for readable output.
Vetted sources

Use the SigmaHQ browser-extension and registry rules, Elastic’s browser extension detections, Splunk ESCU content, MITRE CAR analytics and Atomic Red Team tests for T1176, together with the Chrome and Edge enterprise policy documentation. osquery’s chrome_extensions table gives a clean cross-platform inventory. Baseline your own environment before alerting.

Build Detections That Fire

Mjolnir Security helps blue teams turn threat intelligence into high-fidelity detections across endpoint, network, identity and cloud telemetry.

Detection EngineeringManaged Detection & ResponseThreat HuntingIncident ResponsePurple Teaming
  • Detection Engineering We build and tune detections mapped to ATT&CK from your own telemetry.
  • Threat Hunting as a Service Hypothesis-driven hunts for the behaviours on this page.
  • 24/7 Incident Response Containment when a detection fires. Call +1 833 403 5875.
Written by: Mjolnir Threat Research (MTAC)  |  Published: October 12, 2026  |  Last reviewed: October 12, 2026