嫦美找到我时,整个人是崩溃的 —— “卡颂,我好像被监视了”。
傍晚的星巴克,她的影子被吊灯拉得很长,颤抖着如同她此刻的内心。
“怎么回事?”我尽量让声音听起来平静些。
“最近认识个男生,是我MBA同学,对我很热情,也很懂我”嫦美环顾四周,仿佛随时会有什么东西从夜色中跳出来。
“缘分啊,这不很好嘛?”我笑着说。
“不是那种心有灵犀的懂,是那种「生活起居都被监视的懂」”嫦美解释道。不待我回应,又补充道:“这次约你出来,也是想让你帮忙看看我电脑有没有被植入啥监听木马”。
说罢,从背包里取出MacBook Air递给我。
“Mac一般安全性都蛮高的,你最近没装啥来路不明的应用吧?”一边摆弄她的电脑,我一边问道。
“我也不会装应用,平时主要就上上网、刷刷剧”。
浏览完她的应用列表,我顺手打开了浏览器,又习惯性打开插件列表。
这时,一个浏览器插件吸引了我的注意:
“这是啥?”
“奥,我们MBA的网课需要在这个平台看。这个平台很严,看课不能快进,也不能切换到其他页面。这是那个男同学发我的,装了后就能突破这些限制,还挺方便”说罢,嫦美皱了皱眉“和这个插件不会有关系吧?”
“不好说,等我看看插件源码”。
事实证明,这个插件真的有问题......
浏览器插件为我们上网提供了极大便利,比如:
实际上,浏览器插件除了能「分析并修改原始页面」外,还能:
可以说,有心人只要利用得当,就能通过浏览器插件获得我们上网的所有足迹。
这时,有人会说:“插件能做这些没错,但必须申请必要的权限,我不给他权限不就行了?”
事实真的这么简单么?
《Building Browser Extensions》一书作者「Matt Frisbie」为了演示浏览器插件的潜在安全问题,构造了一个「会申请全部49项权限」的chrome浏览器插件spy-extension[2]。
当你在浏览器安装这个插件后,浏览器确实会提示你「插件申请的权限」:
不过,等等!明明申请了49项权限,这里为什么只显示5项?原来,窗口显示的内容行数有限,超出部分需要拖动滚动条才会显示。
可是,又有几个用户会发现「在申请的5项权限下面,滚动条后面还隐藏了44项权限呢」?
一旦有了权限,想做什么就取决于插件作者的想象力了。可以被用来做坏事的WebExtensions API非常多,比如:
后台运行的Service Worker可以监听发出的网络请求,并在请求发送到网络之前修改它们。
这意味着插件可以使用Service Worker发送数据到服务器,或者在用户浏览网页时拦截请求并发送额外的数据。
由于Service Worker运行于一个独立的后台进程中,所以打开调试工具的Network面板看不到插件发出的请求:
都有哪些有价值的数据可以收集呢?
最简单的,监听用户键盘输入:
[...document.querySelectorAll("input,textarea,[contenteditable]")].map((input) =>input.addEventListener("input", _.debounce((e) => {// 处理 用户输入}, 1000)));
除此之外:
上述API结合Service Worker传输数据,用户在插件作者面前无异于裸奔。
据嫦美表示 —— 她那个MBA同学好像知道她住哪儿,这是怎么做到的呢?很有可能是通过「获取地理位置」的插件功能。
一个网课插件获取地理位置,这不是太奇怪了么?可是嫦美一点都没发觉,这是怎么办到的?
如果插件脚本获取地理位置(通过navigator.permissions.query({ name: "geolocation" })),将询问用户授权。
但如果「被注入脚本的网站」已经获得用户的地理位置授权,插件不需要授权就能静默使用对应功能。
举个例子,如果百度地图向你请求「获取地理位置」的授权,这很合理,你也大概率会同意。
如果恶意插件可以向百度地图注入脚本,当你访问百度地图时,他就不用再获取授权就能访问你的地理位置。
以上所说的所有功能都局限在 —— 插件向已有网站注入脚本。那插件是否能不被察觉的直接打开恶意网站呢?
答案是 —— 可以,我愿称其为「借尸还魂」之法。
而这些「闲置的Tab」就是最好的下手目标。
经常打开很多Tab
首先,插件通过以下代码筛选出闲置的Tab:
const tabs = await chrome.tabs.query({// 筛选用户当前没使用的Tabactive: false,// 筛选用户没有pin的Tab,pin的Tab使用频率通常比较高pinned: false,// 不使用有音频的Tabaudible: false,// 使用已经加载完毕的Tabstatus: "complete",});// 筛选出闲置Tabconst [idleTab] = tabs.filter(/** ...省略其他筛选条件 **/)
只要恶意网站的标题、图标(favicon)与闲置Tab一致,那么用恶意网站替换闲置Tab后,用户也不会有任何察觉。
举个例子,如果闲置Tab是React官网,那恶意网站只需要标题是React,图标是React,即使闲置Tab跳转到恶意网站,从Tab外观上也无法区分。
下面的代码构造了恶意网站的url,其中「与闲置Tab一致的标题、图标」保存在url searchParams中:
// 将标题、图标保存在searchParams中const searchParams = new URLSearchParams({returnUrl: idleTab.url,faviconUrl: idleTab.favIconUrl || "",title: idleTab.title || "",});const url = `${chrome.runtime.getURL("恶意网站.html")}?${searchParams.toString()}`;
恶意网站在url searchParams中取出标题、图标数据,并替换:
// 修改标题document.title = searchParams.get('title);// 修改图标document.querySelector(`link[rel="icon"]`).setAttribute("href", searchParams.get('faviconUrl'));
最后,用恶意网站替换闲置Tab的网站:
await chrome.tabs.update(idleTab.id, {url,active: false,});
恶意网站只需要在「做完坏事后」或「用户重新点击 闲置Tab 时」跳回原来的网站即可。代码如下:
const searchParams = new URL(window.location.href).searchParams;function useReturnUrl() {// 跳回原来网站window.location.href = searchParams.get('returnUrl');}if (document.visibilityState === "visible") {useReturnUrl();}// 用户访问了闲置Tabdocument.addEventListener("visibilitychange", () => useReturnUrl());// ...开始做坏事// 做完坏事,跳回原来网站useReturnUrl();
从用户的视角看,当他点击闲置Tab时,网站重新加载。对于一个闲置的Tab来说,重新访问时加载页面是再正常不过的逻辑。
只是用户不会知道,这并不是「网站重新加载」,而是「退回到前一个网站」。
有人会说 —— 我只使用那些信得过的插件。
但今天信得过的插件,明天就一定信得过么?在暗网中,「用户量大的免费浏览器插件」能卖不错的价钱。
为什么会有人收购这类「没有商业价值的免费插件」呢?一种可能是 —— 收购后向代码中投毒,只要用户升级插件就会中招。
所以,好用的插件不一定没问题,今天没问题的插件明天也不一定没问题。
对于嫦美来说,技术上能做的只能是删除插件、清除缓存、清除cookie,退出所有的账号登录并修改密码。
但似乎更大的危险,来自现实世界......
Let's build a Chrome extension that steals everything:。