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
| Signal | Where | ATT&CK |
|---|---|---|
| ExtensionInstallForcelist or ExtensionSettings policy written under Policies\Google\Chrome or Policies\Microsoft\Edge | Sysmon 13, registry audit | T1176 |
| Browser launched with --load-extension or --disable-extensions-except | Sysmon 1, command-line logs | T1176 |
| Unpacked extension: manifest with no update_url, loaded from outside the Extensions store path | Profile file inventory | T1176 |
| Risky permissions: nativeMessaging, debugger, webRequest, cookies, management, all-URL host access | manifest.json analysis | T1176 |
| Profile Preferences or Secure Preferences modified by a non-browser process | File events, EDR | T1112 |
| New extension ID not in your approved list, or developer mode enabled on managed endpoints | Policy compliance, inventory | T1176 |
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.
- Enforce an allowlist. Block all extensions by default (the
ExtensionInstallBlocklistpolicy with*) and permit only approved IDs, which turns every unknown extension into a policy violation. - Alert on developer mode being enabled on managed endpoints, since unpacked loading depends on it for standard users.
- Prioritise extensions with
nativeMessaging, since they can reach a host binary and move the foothold out of the browser. - Check the extension ID against threat intelligence and the store listing; removed-from-store extensions that remain installed are a common find.
- Include Firefox: review
extensions.jsonandpolicies.jsonin the profile and installation directory.
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 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.
Stay ahead of emerging threats. Get notified when we publish new intelligence reports and advisories.
Subscribe to Alerts