site logo

Marico's space

你的浏览器扩展正在偷窥你:手把手教你做一次完整审计

前端技术 2026-04-30 15:54:10 23

上个月,我做了一件早该做的事:打开浏览器扩展管理页面,逐一查看已安装插件的权限声明——结果差点被咖啡呛到。

一个标签页管理插件,拥有访问我所有浏览数据的权限;一个号称"简单截图"的工具,能读取并修改我访问过的每一个页面;还有一个三年前随手安装、早就忘得一干二净的 CSS 取色器,居然有权限读取剪贴板,并向一些我从未听说过的域名发送网络请求。

如果你是一名开发者,而且最近没有审计过自己浏览器里的扩展程序,那你实际上就是在运行一段来历不明的代码,而且它对你日常工作的访问权限高得吓人。闲话少说,直接讲怎么解决这个问题。

为什么浏览器扩展是一个巨大的攻击面

问题本质很简单:浏览器扩展运行在浏览器的高权限环境中,而你每天的工作——访问代码仓库(不管是 Gitee 还是阿里云Code)、管理云服务器、登录生产环境控制台、处理用户数据——全都在这个环境里进行。

一个恶意或被入侵的扩展可以:

  • 读取页面上所有表单内容(包括密码框)
  • 截获身份认证 Token 和会话 Cookie
  • 向你信任的页面注入脚本
  • 将数据泄露到外部服务器
  • 在没有任何视觉提示的情况下修改页面内容

虽然浏览器应用市场会对扩展进行审核,但这个流程并非万无一失。扩展会被出售给新主人,然后推送恶意更新;依赖库可能被入侵;还有些扩展只是悄悄收集超出实际需要的数据,因为这就是它们的商业模式。

第一步:审计你当前的扩展

先看看你到底装了多少扩展,每个扩展又拿到了哪些权限。

打开浏览器扩展管理页面(Chrome 系输入 chrome://extensions,Firefox 输入 about:addons),开启开发者模式。然后逐个点击"详情",审查每个扩展申请了哪些权限。以下是需要警惕的红灯信号:

// 扩展权限中的红标信号:
"读取和更改您在所有网站上的所有数据"  // 核弹级权限——极少数扩展真正需要这个
"管理您的下载内容"                    // 可以在你电脑上写入文件
"读取您的浏览记录"                    // 数据收集的金矿
"与协同工作的原生应用程序通信"        // 可以与本地进程通话
Enter fullscreen mode Exit fullscreen mode

然后做一次快速清理。卸掉那些你根本不用的扩展。我从 14 个精简到 5 个——说真的,早该这么干了。

对于保留下来的扩展,确认它们是否开源。如果开源,你可以直接读源码再决定是否信任;如果是闭源且申请了大量权限,这是你应该主动去做判断的,不能稀里糊涂地默认接受。

第二步:读懂 manifest.json

每个浏览器扩展都有一个 manifest.json 文件,里面声明了它的所有权限。评估任何扩展源码时,这是第一个要看的东西。

{
  "manifest_version": 3,
  "name": "My Extension",
  "permissions": [
    "activeTab",
    "storage"
  ],
  "host_permissions": [
    "https://specific-api.example.com/*"
  ],
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["content.js"]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

读法如下:

  • permissions:API 级别访问。activeTab 相对安全(只在用户点击时激活当前标签页);而 tabswebRequestcookies 就重多了。
  • host_permissions:扩展可以交互的网站范围。<all_urls> 意味着通行所有网址。一个权限范围得当的扩展只申请它实际需要的域名。
  • content_scripts 配置了 matches: ["<all_urls>"]:这个扩展会向你的每一个访问页面注入 JavaScript。它可以读取 DOM、拦截表单提交、修改页面内容。

最小权限原则在这里完全适用。一个正常的扩展只申请它需要的权限。截图工具不应该需要 cookies 权限,深色模式切换也不应该需要 webRequest

第三步:自己动手做一个(比你想象的容易得多)

让我意外的一点是:写一个基础的浏览器扩展真的很简单。如果你需要某个简单功能却要依赖一个来路不明的扩展,考虑直接自己做一个。

下面是一个最小化的扩展,实现一个真正有用的功能——加一个按钮,把当前页面的标题和 URL 复制为 Markdown 链接:

// background.js
chrome.action.onClicked.addListener(async (tab) => {
  // 格式化为 Markdown 链接
  const markdown = `[${tab.title}](${tab.url})`;

  // 使用 offscreen API 复制到剪贴板
  //(Manifest V3 从 service workers 中移除了直接访问剪贴板的能力)
  await chrome.scripting.executeScript({
    target: { tabId: tab.id },
    func: (text) => {
      navigator.clipboard.writeText(text);
    },
    args: [markdown]
  });
});
Enter fullscreen mode Exit fullscreen mode

对应的 manifest 只需要这些:

{
  "manifest_version": 3,
  "name": "Copy as Markdown Link",
  "version": "1.0",
  "permissions": ["activeTab", "scripting"],
  "action": {
    "default_title": "Copy as Markdown Link"
  },
  "background": {
    "service_worker": "background.js"
  }
}
Enter fullscreen mode Exit fullscreen mode

加载方式:打开 chrome://extensions → 点击"加载解压的扩展程序"→ 选择你的文件夹。就这样,不需要构建步骤,不需要打包工具,也不需要提交到应用商店。

这里的 activeTab 权限是关键——它只在用户明确点击扩展图标时,才授予对当前标签页的访问权,而且仅在点击那一刻有效。对比一下:一个做同样事情的扩展,如果申请了 <all_urls>,那权限差距就天壤之别了。

第四步:用脚本自动化审计

如果想做得更彻底,可以写脚本自动化审计流程。Chrome 会把扩展数据存在一个固定位置:

# macOS
ls ~/Library/Application\ Support/Google/Chrome/Default/Extensions/

# Linux
ls ~/.config/google-chrome/Default/Extensions/

# 逐一检查每个扩展的 manifest
for dir in ~/Library/Application\ Support/Google/Chrome/Default/Extensions/*/; do
  latest=$(ls -t "$dir" | head -1)
  manifest="$dir$latest/manifest.json"
  if [ -f "$manifest" ]; then
    name=$(python3 -c "import json; print(json.load(open('$manifest')).get('name','unknown'))")
    perms=$(python3 -c "import json; print(json.load(open('$manifest')).get('permissions',[]))")
    hosts=$(python3 -c "import json; print(json.load(open('$manifest')).get('host_permissions',[]))")
    echo "Extension: $name"
    echo "  Permissions: $perms"
    echo "  Host access: $hosts"
    echo ""
  fi
done
Enter fullscreen mode Exit fullscreen mode

运行之后,你就能快速拿到所有扩展及其申请权限的汇总。导出到文件,找个时间慢慢审查——你可能会惊讶于发现的东西。

预防:从此以后的行为准则

完成这轮审计之后,我给自己立了几条规矩:

  • 优先使用开源扩展。如果能读源码,就能验证它的实际行为。现在已经有不少开发者专门把扩展以开源项目(放到 Gitee 或 GitHub)的方式发布,来解决信任问题——这种做法值得支持。
  • 关注更新频率和所有权变更。某个扩展半年前换了主人,然后突然连推了三次更新?值得警惕。
  • 使用独立的浏览器配置文件。我的"工作"配置文件中只保留最核心的几个扩展;日常浏览的配置文件才是试验新扩展的地方。生产环境的凭证和来源不明的扩展,绝对不能共存于同一个配置文件。
  • 可能的情况下锁定扩展版本。如果你以加载解压扩展的方式从源码运行,更新节奏就完全由你掌控。
  • 每次更新后重新审查权限。扩展在更新时可以申请新的权限。浏览器有时候会提示,但并不是每次权限变更都会通知你。

更大的图景

浏览器扩展生态存在的信任问题,其实和我们在 npm 包、VS Code 扩展,以及所有插件市场看到的如出一辙。攻击面巨大,审核流程有漏洞,大多数用户从不深究。

但作为开发者,我们有一个独特的优势——我们能读代码。我们看得懂 manifest.json,能为自己需要的简单功能编写替代方案,遇到维护良好且开源的扩展时,应该优先选择它们,而不是闭源方案。

不是说所有事情都要从零开始。但你至少应该知道自己浏览器里运行着哪些代码,它们对你的核心工作流有着怎样的访问权限。今天抽出半小时审计一下你的扩展,未来你会感谢现在的自己。