继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

【XSS】利用 onload 事件监控流量劫持

aluckdog
关注TA
已关注
手记 307
粉丝 68
获赞 392

说到跨站资源监控,首先会联想到『Content Security Policy』。既然 CSP 好用,我们何必自己再搞一套呢。那就先来吐槽下 CSP 的缺陷。

目前的 CSP

日志不详细

用过 CSP 的都很郁闷,上报的只有违规的站点名,却没有具体路径。这是缺陷,还是特意的设计?

显然,CSP 是为安全定制的,里面的规范自然要严格制定,否则就会带来新的安全问题。如果支持详细路径的上报,那又会引出什么问题?

由于 CSP 会上报所有的请求,甚至包括重定向的,因此可以用来探测重定向后的地址。假如已登录的用户访问 login.xx.com 会重定向到 xx.com/username,那么攻击者设计一个只允许重定向前的规则的页面,用户访问后,重定向后的 URL 就会当做违规地址上报给攻击者,这其中就包括了用户名。

如果支持详细路径的上报,这简直就是灾难,就用来探测的用户隐私信息了。事实上目前只上报主机名,都能进行一些利用,例如这篇 Using Content-Security-Policy for Evil。

不过新的规范总是在改进,未来也许只上报重定向前的 URL。但在这之前,我们只能接受这些鸡肋的上报日志。

规则不灵活

CSP 目前只支持白名单列表,这多少有些死板。

更糟的是,不同规则之间无法继承和共享。例如默认有个 default-src 规则,但其他的规则会覆盖它,而不是继承它。这就导致各个规则之间,出现很多的重复,使得整个字符串变的冗长。

无法和页面交互

CSP 的监控和上报,是在浏览器后台自动处理的,没有提供一个事件供页面进行交互。

这样就只能使用统一方式强制处理了,而无法交给页面脚本,更好的来自定义处理。

上报方式不可控

如果处理方式有多种选择,那么统一处理也无可厚非。

但事实上 CSP 的上报方式及格式,没有任何可选余地。只能使用 POST + JSON 的方式提交,并且其中的字段十分累赘,甚至把规则里的白名单列表也发上来了。

此外,也无法设定一个缓存时间,控制重复上报的间隔。在配置白名单遗漏时,会出现大量的误报,严重消耗资源。

浪费带宽

在较新的 Chrome 里,能够使用 meta 标签在前端页面定义 CSP 规则,但其他浏览器目前仍不支持。

为了能够统一,大多仍使用 HTTP 头部输入的方式。由于规则通常都很长,导致每次页面访问,都会额外增加数百字节。

维护繁琐

如果是通过 Web 服务开启的,那么每次调整策略,都得修改配置甚至重启服务,很是麻烦。

兼容性不高

目前只有高版本的浏览器支持,而 IE 系列的则几乎都没能很好的支持。

如果某些攻击只争对低版本的浏览器,那么很有可能出现大量遗漏。

模拟的 CSP

原理

事实上在 CSP 出现的好几年前,就有一个能够监控跨站资源的方案,下面就来分享下。

写过 JS 的都知道,如果需要给大量元素监听事件,无需对每个元素上都进行绑定,只要监听它们的容器即可。当具体的事件冒泡到容器上,通过 event.target 即可获知是哪个元素产生的。

脚本、图片、框架等元素加载完成时,都会产生 onload 事件;而所有元素都位于『文档』这个顶级容器。因此我们监听 document 的 onload 事件,即可获知所有加载资源的元素。

不过 onload 这个事件比较特殊,无法通过冒泡的方式来监听。但在 DOM-3 标准模型里,事件还有一个『捕获』的概念,这也是为什么 addEventListener 有第三个参数的原因。

我们通过事件捕获机制,将其拿下,从而监控文档级别的全局 onload 事件。

<script>document.addEventListener('load', function(e) {    console.log(e.target);}, true);</script><script src="https://libs.baidu.com/jquery/1.9.0/jquery.js"></script><iframe src="https://www.baidu.com/"></iframe>

类似的,如果资源加载失败,会触发 onerror 事件。我们也可同时将其捕获,跟踪那些暂时不可用的跨站资源。

优势

通过脚本的方式,就可以更灵活的处理问题了。规则的黑白名单,上报方式、格式等等,都可以自己来定义。最重要的是,我们能够获得详细的违规 URL 了!

相比后端配置,前端脚本更新维护起来容易的多。而且得益于浏览器缓存,无需每次更新配置,节省很多资源。

增强

也许你已经发现了,这只能实现 CSP 的 Report-Only 功能,根本无法进行拦截。而且其他的内联事件、网络通信等也没有涉及。

不过本文的主题已经说了,只是监控跨站资源而已,并不拦截。事实上,如果能够做到及时发现问题,就很不错了。

如果非得通过 JS 来实现拦截功能,可以参考之前的『XSS 前端防火墙系列』:

  • 内联事件拦截

  • 跨站资源拦截

  • 网络通信拦截

虽然可以更严格的防护,但实现起来更臃肿,性能开销也更大。要是拦截了正常的业务功能,造成的损失会更大。

而使用如今这个小技巧,只需几行代码即可实现,性能消耗忽略不计。

缺陷

当然,那么简单的方案肯定无法全面。

由于我们只监听文档容器,有些还未加入到文档的元素产生的事件,我们就无法捕获到了。最典型的就是:

new Image().src = '...'

虽然创建的 HTMLImageElement 对象具有 onload 事件,但是此时还只是一个离屏元素,事件就无法对外传播了。

如果非得解决,只能通过函数钩子的方式监控 URL。

此外,IE9 以下的浏览器不支持 DOM-3,而 attachEvent 是无法设置捕获的,因此我们还需一个后备方案。毕竟国内低版本 IE 用户仍有不少,即使能实现部分功能,也胜于无。

后备

事实上,即使主流浏览器,有些特殊元素并没有 onload 事件,例如 Flash 插件。

为了弥补这些不足,同时尽可能保持简单高效,我们使用定时轮询的方式,对特定元素进行扫描。这里使用一个大家都知道,但未必都清楚的方法:document.getElementsByTagName。

这个功能都知道,但返回的类型或许很少琢磨。他返回的并不是一个 Array,也不是 NodeList,而是 HTMLCollection。

在 W3C 规范描述中,有一个显眼的词 『live』,已经道出了这个接口的独特之处——它是一个动态的集合,能随着容器内元素的增减而变化

因此,我们事先映射出文档容器内的元素,之后即可随时查询集合了。

<button id="btn">Load Script</button><div id="stat"></div><script>    function log(str) {        var line = document.createElement('div');        line.innerHTML = str;        stat.appendChild(line);    }    // 这是个动态集合,只需查询一次即可!    var colScript = document.getElementsByTagName('script');        setInterval(function() {        var display = [];        // 可以访问到最新的记录        for (var i = colScript.length - 1; i >= 0; i--) {            var el = colScript[i];            if (el.src) {                // check url                display.push(el.src);            }        }        log('num: ' + display.length + ' list:' + display.join(',') );    }, 1000);    // [test] load script    btn.onclick = function() {        var el = document.createElement('script');        el.src = 'http://libs.baidu.com/jquery/1.9.0/jquery.js';        document.body.appendChild(el);    }</script>

除了 SCRIPT,我们还可以监控其他存在隐患的元素,例如:EMBED,OBJECT,IFRAME 等等。这样即使是低版本的 IE 用户,也能参与预警上报了,比起完全没有好的多。

当然,这种简单方法也很容易被绕过。如果脚本删除了自身元素,那么我们就无法跟踪到了。而脚本一旦运行就已在内存里,即使元素节点被移除,仍然能继续运行。

不过,对于一般的情况也足够应对了。通常运行商的广告劫持,大多都很落后。即使要进行后期对抗,也可以利用之前的前端防火墙,使用严格的方案。

扩展信息

见过 CSP 日志的大多都会很困惑,出现的这些违规资源,究竟位于页面何处。由于没有确切的细节,给排查工作带来很大困难。

毕竟,绝大多数的上报问题,都是无法复现的。它们要么是运行商的广告,或者是浏览器插件。难很通过这些日志,来定位问题所在。

既然如今使用自己的脚本来实现,理应带上一些有意义的信息。除了资源类型和详细 URL,我们还需要一个能够定位问题所在的参数——DOM 路径。

将每层元素的 #id 和 .class 跟随标签名,得到一个标准的 CSS 选择器。通过它,即可非常容易的定位到违规元素,同时也能用于统计和分析。

例如出现顶级元素就是 SCRIPT 的,显然这是一个被插入到 <html> 之外的脚本,很有可能就是运营商注入的广告脚本。

例如出现 HTML > BODY > ... > DIV.editor-post > SCRIPT 这样帖子容器里的脚本,那么极有可能是出现 XSS 了。正常情况下,用户内容区域并不会出现脚本元素。

通过详细的上报日志不断学习,后端即可越来越精准的分析出问题所在。

而这些,目前的 CSP 难以实现。

上报方式

吸取 CSP 报告的不足之处,我们使用更灵活的方式。

对于运营商广告那样的跨站资源,反复上报同样的信息,是毫无意义的,只会浪费带宽资源。对于同一类警告,一定时间内上报一次就后够了。

利用 URL、DOM 路径等信息,可以更容易的将日志进行客户端去重。通过本地存储的记录,就不必每次都上报了,在本地记录违规的次数即可。

对于不严重的警告,本地也可以累计到一定的数量再上报。这样多条日志一次发送,即可大幅减轻后端压力。甚至还可以考虑压缩内容,节省网络带宽。

只有让前端进行负载维护,才不至于大规模部署的场合下,接收端被海量的误报日志拖垮。

总结

尽管 CSP 的初衷很美好,但到如今,仍只是能用,并没有做到好用。因此,实际面临的问题,还是得靠自己来解决。

当然,标准的制定本来就是受益于大众的,我们也希望 CSP 标准能发展的越来越好用。

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP