一提到 CSP 大家可能会想起 XSS,没错,CSP 诞生之初就是为了防御层出不穷的 XSS 漏洞的(虽然目前看起来效果并不好),不过利用它的特性,也能有其他的用途,比如…… 反运营商劫持。

在中国这个神奇的国度,一款产品用户多了,必然会出现这样的反馈——「你们的网站/APP上怎么总是有色情广告?」。当查来查去最后发现是运营商搞的鬼时该怎么办?去工信部投诉?能否解决另说,怎么找出各个地区的各种违规的运营商就已经让人头大了。于是有人想到了前端防御:维护一个黑名单,当前端检测到有黑名单元素存在页面上时就把它干掉。

下面是一段某 APP 检查其 Webview 加载网站的代码:

    xxx.thirdparty.jsonp_callback(
        {
        "status": "success",
        "result": ".banner",
        "customize": "if(document.querySelector('div[xxx-ignored-node=\"1\"]')){document.querySelector('div[xxx-ignored-node=\"1\"]').style.display='none';}"
        }
    )

这种靠黑名单来进行前端拦截的方式可能会有些效果,但弊端也很明显:

  1. 黑名单主要靠用户反馈维护,麻烦且时效性差。
  2. 黑名单数量过多可能会影响性能。
  3. 劫持网站/IP太多,很容易被绕过。

而 CSP 使用白名单机制,目前主流浏览器也支持,所以经过调研后决定采用 CSP 作为解决方案。(最近看到 QQ 邮箱也加上了 CSP,但它的 script-src 里有 unsafe-inline,不太可能是用来防 XSS 的)。

在加上 CSP 之前,我们先看看运营商劫持的主要手法:

  1. DNS 劫持。直接篡改 DNS 返回包,比例很小,除了让用户修改 DNS 没什么好办法。
  2. HTTP 劫持。全部或部分替换/注入页面元素,比例很大。

    • 遇到返回包全量替换(整个页面或 JS 文件),请用 HTTPS。
    • 遇到 script、iframe 等注入,可以用 CSP 防御。

对于 CSP 白名单的获取,打开网页后抓包看看加载了哪些外部资源即可,推荐一个在线工具 Report-URI CSP Generate
线上小量灰度测试时可以用 Content-Security-Policy-Report-Only,这将只发送违规资源而不会拦截它。
设置 report-uri 后浏览器会自动向接收地址 POST 违规资源详情,后端只需把数据丢给 ELK 就可以了。

一个典型的运营商劫持案例:

image2017-9-27_18-55-38.png

可以看到劫持都发生在同一个省,同一个运营商,劫持次数变化符合作息时间。

对劫持数据聚合分析后发现 湖南电信 和 广东电信 是劫持最多的运营商:

15149813361479.jpg

绕过 CSP 的情况

除部分内核较老的浏览器使用 CSP 会有奇怪的 bug(如不支持 script-src),还有一些劫持方法可以绕过 CSP,这里暂不展开……

其他劫持情况

实际运行中发现了一些非运营商的劫持:

  1. Wi-Fi,某些路由器会在 HTTP 流量中插入广告,银行,酒店,医院等公共场所的 Wi-Fi 劫持也比较普遍。
  2. 浏览器插件。某些插件会在用户浏览的每个页面都插入广告。
  3. 恶意软件。发现一些劫持地区很广,各个运营商都存在的域名,和恶意软件有关。
  4. VPN。某些 VPN 或黑心梯子商会劫持流量插广告。

image2017-9-29_21-38-33.png

CSP 的其他用法

开发反运营商劫持系统时有一些其他的脑洞:

  1. 广告拦截插件。现在的浏览器广告拦截插件都是基于黑名单的,如果做一个基于 CSP 的广告拦截插件应该效果不错,毕竟维护白名单比维护黑名单轻松多了。
  2. 内部网站盲打监控。外部网站因为业务需要往往不会上 CSP 防御 XSS,不过一些内部网站,比如用户反馈平台,可以接入 CSP 系统,防止 XSS 盲打。

参考