浏览器扩展权限怎么审?企业白名单配置实战

浏览器扩展权限审计,是终端安全里最容易被跳过的一环。绝大多数企业管住了系统补丁、管住了管理员权限,却对本机 Chrome 里装了什么扩展一无所知。一个申请了 debugger、cookies 和全站主机访问权的扩展,能力等价于在浏览器进程里放了一个常驻调试器:它能读走所有站点的登录态,能改写页面里的转账账号,也能把 Cookie 直接外传。2026 年多起浏览器扩展被收购后静默转向的事件说明,扩展的”当时无害”不代表”长期可信”。

适用场景

以下三类环境最需要做这件事:

  • 办公终端统一管理:员工自行安装扩展,IT 没有清单,出了问题无法回溯是哪个扩展导致的。
  • 财务、运营等敏感岗位:这些岗位日常登录后台系统,Cookie 价值高,是扩展类窃密的首要目标。
  • 发生过账号异常但查不到原因:日志里只有正常的登录记录,没有暴力破解痕迹,这时候要怀疑浏览器侧。

不建议把这件事做成”一次性清查”。扩展会自动更新,权限会在新版本里扩张,审计必须是可重复执行的流程,而不是一份 Excel。

前置条件

  • Chrome 或 Edge 版本不低于 96,ExtensionSettings 策略在该版本起完整支持 runtime_blocked_hosts。
  • Windows 终端需有域策略或本地管理员权限写 HKLM;Linux 终端需 root。
  • 一台可以运行 Python 3.8 以上版本的分析机,用于批量打分。
  • 已有一份扩展白名单的业务依据,否则技术做完仍然无法判断”该不该封”。

原理说明:扩展权限到底能做什么

扩展的能力边界由 manifest.json 声明,浏览器在安装时一次性授权,之后扩展可以在不再次询问用户的情况下使用这些能力。

三类关键权限

  • permissions:API 级能力。cookies 可读所有站点 Cookie,debugger 可附加到标签页执行任意脚本,proxy 可改全局代理设置把流量导向自建服务器,nativeMessaging 可与本机程序通信,等于突破浏览器沙箱。
  • host_permissions:站点范围。写成 <all_urls> 意味着对用户访问的每一个站点都有读写 DOM 的能力,风险等级比任何单个 API 权限都高。
  • content_scripts:注入脚本。即使没有 host_permissions,只要 matches 覆盖目标站点,同样能读取页面内容并回传。

策略的优先级

企业下发的 ExtensionSettings 优先级高于用户操作。策略里 installation_mode 设为 blocked 的扩展无法安装,已安装的会被禁用;设为 force_installed 的扩展用户无法卸载。这一点很关键:光靠通知员工卸载是无效的,必须走策略。

操作步骤

第一步:清点终端已安装扩展

Chrome 与 Edge 的扩展元数据都落在用户配置目录下,直接读文件比翻界面可靠。以下脚本在终端本机执行,输出一份 CSV 清单:

$paths = @(
  "$env:LOCALAPPDATA\Google\Chrome\User Data\Default\Extensions",
  "$env:LOCALAPPDATA\Microsoft\Edge\User Data\Default\Extensions"
)
$rows = foreach ($p in $paths) {
  if (-not (Test-Path $p)) { continue }
  Get-ChildItem $p -Directory | ForEach-Object {
    $extId = $_.Name
    Get-ChildItem $_.FullName -Directory | ForEach-Object {
      $mf = Join-Path $_.FullName "manifest.json"
      if (-not (Test-Path $mf)) { return }
      $j = Get-Content $mf -Raw -Encoding UTF8 | ConvertFrom-Json
      [PSCustomObject]@{
        ExtId   = $extId
        Name    = $j.name
        Version = $j.version
        Perms   = ($j.permissions -join ",")
        Hosts   = ($j.host_permissions -join ",")
        Source  = $p
      }
    }
  }
}
$rows | Export-Csv -NoTypeInformation -Encoding UTF8 ext-inventory.csv
"共采集 $($rows.Count) 条扩展记录"

输出字段里 Perms 是 API 权限,Hosts 是主机权限。如果某条记录的 Hosts 为空但扩展仍能读取页面,说明它用的是 content_scripts,需要额外看 manifest 里的注入配置。

第二步:按权限打分排序

几十台终端、上百个扩展,靠人眼看不过来,先用权重模型把高危的顶到前面:

import csv

# 权限风险权重:数值越高,越接近"可完全接管浏览器"
WEIGHT = {
    "debugger": 5, "management": 5, "proxy": 4, "nativeMessaging": 4,
    "scripting": 4, "cookies": 3, "clipboardRead": 3, "webRequest": 3,
    "declarativeNetRequest": 3, "history": 2, "downloads": 2,
    "tabs": 1, "storage": 0,
}

rows = []
with open("ext-inventory.csv", encoding="utf-8-sig") as f:
    for r in csv.DictReader(f):
        perms = [p.strip() for p in (r["Perms"] or "").split(",") if p.strip()]
        hosts = (r["Hosts"] or "").strip()
        score = sum(WEIGHT.get(p, 0) for p in perms)
        if hosts in ("<all_urls>", "*://*/*"):
            score += 5
        elif hosts:
            score += 2
        rows.append((score, r["ExtId"], r["Name"][:26], ",".join(perms)[:56]))

rows.sort(reverse=True)
print(f"{'分':<4}{'扩展ID':<34}{'名称':<28}权限")
for s, i, n, p in rows[:25]:
    print(f"{s:<4}{i:<34}{n:<28}{p}")
print(f"\n总分大于 8 的扩展:{sum(1 for r in rows if r[0] > 8)} 个")

打分不是目的,排序才是。经验上总分大于 8 的扩展应当逐个确认来源,总分大于 12 且不在白名单里的,直接进封禁候选。

第三步:下发 ExtensionSettings 策略

策略文件是一段 JSON,默认放行、点名强制、点名封禁:

{
  "ExtensionSettings": {
    "*": {
      "installation_mode": "allowed",
      "blocked_install_message": "该扩展未通过安全评审,请向 IT 提交申请单。"
    },
    "aeblfdkhhhdcdjpifhhbdiojplfjncoa": {
      "installation_mode": "force_installed",
      "update_url": "https://clients2.google.com/service/update2/crx"
    },
    "fdpohaocaechififmbbbbbknoalclacl": {
      "installation_mode": "blocked"
    }
  }
}

通配符 * 的 allowed 表示用户仍可自行安装其他扩展,只是会看到提示语。如果要做到”只能装白名单”,把通配符改成 blocked,再逐个列出需要放行的 ID。后一种更严格,但对员工体验冲击大,建议先跑一个月 allowed 观察申请量。

Windows 端写入注册表,注意字典类型的策略在注册表里以 JSON 字符串形式存放:

$json = Get-Content .\chrome-extensions.json -Raw -Encoding UTF8
$key  = "HKLM:\SOFTWARE\Policies\Google\Chrome"
New-Item -Path $key -Force | Out-Null
Set-ItemProperty -Path $key -Name "ExtensionSettings" -Value $json -Type String
Set-ItemProperty -Path $key -Name "ExtensionInstallAllowlist" -Value @() -Type MultiString

# Edge 同一套策略对象,只换注册表根
$keyEdge = "HKLM:\SOFTWARE\Policies\Microsoft\Edge"
New-Item -Path $keyEdge -Force | Out-Null
Set-ItemProperty -Path $keyEdge -Name "ExtensionSettings" -Value $json -Type String

gpupdate /force | Out-Null
"策略已写入,终端刷新 chrome://policy 后生效"

Linux 端更简单,策略目录放文件即可,Chrome 为 /etc/opt/chrome/policies/managed/,Edge 为 /etc/opt/edge/policies/managed/,文件名任意,后缀必须是 .json。

第四步:收敛运行时主机访问范围

有些扩展业务上确实需要放行,但不需要访问全部站点。这时用运行时主机限制把它圈在允许的域内:

{
  "ExtensionSettings": {
    "aeblfdkhhhdcdjpifhhbdiojplfjncoa": {
      "installation_mode": "force_installed",
      "runtime_blocked_hosts": [
        "*://*.internal.corp.example",
        "*://10.20.0.0/16",
        "*://admin.example.com"
      ],
      "runtime_allowed_hosts": ["*://wiki.example.com"]
    }
  }
}

runtime_blocked_hosts 的过滤发生在扩展发起请求时,命中即被浏览器拒绝,扩展侧拿不到任何响应内容。它不阻止扩展加载,只是切断它对指定主机的访问,因此适合”功能保留、范围收窄”的场景。

第五步:封禁与强制移除

blocked 只是禁用,扩展文件仍在磁盘上。要彻底移除,在策略对象里改用 removed:

{
  "ExtensionSettings": {
    "fdpohaocaechififmbbbbbknoalclacl": {
      "installation_mode": "removed"
    }
  }
}

这两个模式的区别值得记住:blocked 保留扩展数据,日后解封可以恢复配置;removed 会清掉扩展的本地存储,属于不可逆操作。对确认的恶意扩展用 removed,对”暂不确定”的用 blocked。

配置验证

策略下发后必须验证三件事:文件语法正确、浏览器真的读到了、运行时确实生效。

# 1) 策略文件语法校验
jq . /etc/opt/chrome/policies/managed/extensions.json > /dev/null && echo "JSON OK"

# 2) 确认策略文件被 Chrome 读取(策略目录必须归属 root 且不可写)
stat -c "%U:%G %a %n" /etc/opt/chrome/policies/managed/extensions.json
# 期望输出:root:root 644

# 3) 终端侧核对:打开 chrome://policy,点"重新加载政策"
#    ExtensionSettings 行状态应为 OK,且不出现在"未应用"列表

浏览器侧还要确认策略没有被用户配置文件覆盖。在地址栏输入 chrome://policy,展开 ExtensionSettings,状态为 OK 且”来源”显示为平台策略,才算真正生效。如果显示 Not applied,通常是 JSON 语法错、策略目录权限过宽,或注册表键名拼写错误。

最后做一次周期性比对,把”装了但不在名单里”的扩展捞出来:

# 从策略里取出已批准扩展 ID 与安装模式
jq -r '.ExtensionSettings | to_entries[] | [.key, (.value.installation_mode // "allowed")] | @tsv' \
  /etc/opt/chrome/policies/managed/extensions.json \
  | sort > approved.tsv

# 从终端清单里取出实际安装的扩展 ID
tail -n +2 ext-inventory.csv | cut -d, -f1 | sort -u > installed.tsv

# 差集:装了但不在策略名单里的(需人工判定是允许还是封禁)
comm -23 installed.tsv approved.tsv

这个比对建议做成每周任务,输出直接进安全运营群。扩展自动更新后重新申请权限的情况,就靠这个差集发现。

常见问题

Q1:策略下发后扩展还在,只是显示”由贵单位管理”,是不是没生效?

这是生效的正常表现。Chrome 对被策略管理的扩展会显示”由贵单位管理”标签,扩展仍然加载,但用户无法卸载、无法关闭。force_installed 的设计目的就是”必须存在”,所以它不会消失。要确认是否真的受控,看 chrome://policy 的状态列,而不是看扩展是否还在列表里。

Q2:runtime_blocked_hosts 配置了,扩展照样能读到页面内容,为什么?

大概率是扩展通过 content_scripts 直接注入,而不是走网络请求。runtime_blocked_hosts 只拦截扩展发起的请求,管不到内容脚本在页面上下文里的执行。对于这类扩展,只能靠 host_permissions 层面的策略收窄,或者干脆封禁。判断方法:在 chrome://extensions 打开开发者模式,看该扩展的”检查视图”是否在目标页面上存在。

Q3:员工用绿色版或便携版浏览器,策略还能管住吗?

不能。便携版浏览器通常不读系统策略目录,注册表策略也可能被绕过。这类情况要从终端管控层解决:应用白名单只允许安装受管版本,或者用 AppLocker、WDAC 限制可执行文件路径。策略是浏览器层的约束,前提是浏览器本身受管。

总结

浏览器扩展权限审计的核心不是写一条策略,而是建立”清点—打分—下发—比对”的闭环。清点解决看不见的问题,打分解决优先级问题,策略解决执行力问题,比对解决扩展更新后权限悄悄扩张的问题。四步里最容易漏掉的是最后一步:扩展不是静态资产,它会自己变,审计也必须持续运行。对中小企业来说,先做一份全量清单,把 <all_urls> 加 cookies 的组合挑出来处理,收益已经足够覆盖成本。