*本文原创作者:lufei,本文属FreeBuf原创奖励计划,未经许可禁止转载
0x00 前言
Burp虽然自带xss检测,但是Pyload与数量都不是自己能掌控的。所以自己写一款Xss检测插件,对一个参数进行测试的时候,要求只能发送一次Payload(检测能否进行逃逸当前分隔符),而且能够对Dom Xss进行检测。
0x01 检测思路
最开始的设想是发送带'"<>的http 请求,看能否返回'"<>这些字符,对于检测html-content xss 和 js-content xss的情况很好处理,但是无法检测dom xss。需要渲染页面,于是使用java中的htmlunit进行页面渲染,来判断'"<>的过滤情况(由于进行页面渲染,会自动修改错误的html语法),以及函数hook,改写js中的对象函数进行判断是否存在dom xss。
0x02 成品
html-content xss检测
js-content xss检测
dom xss检测
0x03 html-content xss检测
其实本来这个是最容易,但是上面提到htmlunit会自动修正错误的html语法,也需要提及一下。
使用如下测试函数,对渲染后的代码进行测试。
@org.junit.Test
public void CheckHtmlXss() throws IOException {
WebClient webClient = new WebClient();
webClient.getOptions().setJavaScriptEnabled(true);
webClient.getOptions().setCssEnabled(false);
webClient.getOptions().setUseInsecureSSL(true);
webClient.getOptions().setThrowExceptionOnScriptError(false);
//获取页面
try
{
String url ="http://127.0.0.1/test2.php?url=xxxxx";
HtmlPage page = webClient.getPage(url);
System.out.println(page.asXml());
} catch (ScriptException e) {
System.out.println(e.getFailingLine());
System.out.println(e.getPage().asXml());
}
webClient.close();
}
测试下面这个xss。
<?php
$url = $_GET['url'];
echo "<img src='".$url."'>";
?>
如果发送的请求是
test.php?url=TTT'"<>TTT
则使用htmlunit渲染的页面源码是
<html>
<head/>
<body>
<img src="TTT" "<=""/>
TTT'&gt;
</body>
</html>
发现我们的payloadTTT'"<>TTT
已经完全被打乱了。所以我们需要闭合前面的img
标签。再发送如下请求。
test.php?url='"/>TTT'"<>TTT
返回包
<img src="" "=""/>
TTT'"&lt;&gt;TTT'&gt;
发现payload中的<>
被转义了。如果我们往<>
填入一些字符尼?
test.php?url='"/>TTT'"<xxx>TTT
返回包
<img src="" "=""/>
TTT'"
<xxx>
TTT'&gt;
</xxx>
可以发现已经当成了<xxx>
标签解析了,虽然<>
没被转义,但是却不好正则匹配
TTT'"
<xxx>
TTT'&gt;
这结果是三行,正则使用的是单行匹配,多行匹配的话,可能会匹配到我们不想匹配到的东西。
那把TTT'"TTT
放入<>
,是否当作标签解析。
test.php?url='"/><TTT'"TTT>
返回包
<img src="" "=""/>
<ttt '"ttt="">
'&gt;
</ttt>
现在'"<>
都在一行了,可以使用如下正则<ttt(.*?)'"ttt(.*?)>
进行匹配。
但是遇到过滤了<>"
的html xss,这个xss只要没过滤'
还能进行x的。
<?php
$url = $_GET['url'];
$url = str_replace("<","00",$url);
$url = str_replace(">","11",$url);
$url = str_replace("\"","22",$url);
//$url = str_replace("'","22",$url);
echo "<img src='".$url."'>";
?>
请求payload
test.php?url='"/><TTT'"TTT>
从返回包可以看到,htmlunit删除了错误的000ttt’"ttt111
内容,使用的payload又失效了。又没办法进行验证是否存在xss了。
<html>
<head/>
<body>
<img src="000111&quot;"/>
</body>
</html>
不过从上面的实验中可以看出,htmlunit在修正语法的时候,标签名和标签属性是不会被转义和删除的。于是我们不闭合标签,更改payload。
test.php?url=000111"'<TTT'"TTT>
返回包
<img src="00011122" 00ttt'22ttt11'=""/>
这样就可以进行可以检测出没有过滤'
,这样就可以让burp报html-content xss: the separator is ',but can escape.
了。这样验证是否有利用的空间,至于再具体的利用,就需要手动测试了。
总结下html-content的检验,检测 html-content xss应该很简单的,不使用htmlunit渲染进行匹配,就很容易检测出来的。但是考虑dom xss的存在,又不想发送两次请求包。只能根据htmlunit进行语法修正的后的源码调整payload。
0x04 js-content xss检测
在这个插件中,检测js-content xss最容易,因为没有对错误的无法进行修正。
<?php
$url = $_GET['url'];
?>
<script>
var a = <?php echo $url;?>
</script>
请求
test.php?url=000111"'<TTT'"TTT>
返回包
<html>
<head>
<script>
//<![CDATA[
var a = "000111"'<TTT'"TTT>";
//]]>
</script>
</head>
<body/>
</html>
使用正则<ttt(.*?)'"ttt(.*?)>
进行匹配。判断是否能逃逸当前的分隔符,则使用正则判断,比如 '(.*?)ttt(.*?)'(.*?)ttt(.*?)'
如果能匹配到结果,则说明能够逃逸分号。分号的逃逸判断类似。
0x05 dom xss检测
如何检测dom xss,绕了很多弯。比较幸运的是htmlunit
是支持返回包的修改的(这样就可以对一些函数进行hook,修改,达到检查dom xss的目的)。
使用如下测试函数,进行返回包的js函数修改、hook。
public class WebConnectionListener extends FalsifyingWebConnection {
public WebConnectionListener(WebClient webClient) throws IllegalArgumentException {
super(webClient);
}
@Override
public WebResponse getResponse(WebRequest request) throws IOException {
WebResponse response = super.getResponse(request);
String url = response.getWebRequest().getUrl().toString();
return createWebResponse(response.getWebRequest(), reResponse, response.getContentType(), response.getStatusCode(), "Ok");
}
}
@org.junit.Test
public void CheckDomXss() throws IOException {
WebClient webClient = new WebClient();
webClient.getOptions().setJavaScriptEnabled(true);
webClient.getOptions().setCssEnabled(false);
webClient.getOptions().setUseInsecureSSL(true);
webClient.getOptions().setThrowExceptionOnScriptError(false);
new WebConnectionListener(webClient);
try
{
String url ="http://127.0.0.1/test.php?id=111";
HtmlPage page = webClient.getPage(url);
System.out.println(html);
} catch (ScriptException e) {
System.out.println(e.getFailingLine());
System.out.println(e.getPage().asXml().toLowerCase());
}
webClient.close();
}
下面是 document.write
、innerHTML
造成的xss,页面进行渲染后,前面的html-content xss检测过程就能检测出来的,但是eval
、location
、SetTimeout
函数确实没办法检测出来的。后来想起以前看的js反调试中的hook,能够监控这些函数的参数。在翻资料的时候,发现有前人已经使用hook进行检测dom xss,不过文章很复杂,而我们只需要hook几个简单的函数就可以了。
hookeval
、SetTimeout
函数很顺利。但是location = "xss"
、location.href="xss"
、location.replace("xss")
却遇到很大的障碍。因为这里是赋值操作,而且更为难得是,无法对location进行修改。
首先遇到问题是,在chrome无法对location
对象进行修改,location.href = "xss"
,这赋值操作,如何拦截尼?幸运的是在htmlunit还能够使用__defineSetter__
方法进行拦截。
location.__defineSetter__('href', function(url) {
append("location",url);
});
接下来location = "xss"
如何监控尼?这个纠缠了很久,翻了很多资料,包括看之前那篇hook js检测dom xss的文章(那篇文章也有人在问如何检测location),都没有解决方案。突然既然能修改返回报文,我直接在返回报文中把location = "xss"
修改成location.href = "xss"
。使用上面的方法就可以进行监控了。
最后是location.replace("xss")
这个函数也是无法进行hook的,那我们直接修改location.replace
为_replace
,
var _replace = function(url)
{
append("location.replace",url);
};
上面的append函数,是判断函数参数或者赋的值,是否包含我们payload字符串,如果包含,则添加一个特定的p标签。插件以此判断该p标签的内容,报告xss漏洞。完整的监控的代码。
<script>
function append(type,payload)
{
if(payload.indexOf("111000")>-1)
{
var para=document.createElement("p");
var node=document.createTextNode(type + payload);
para.appendChild(node);
var element=document.getElementsByTagName("html")[0];
element.appendChild(para);
}
}
var _eval = eval;
window.eval = function(string) {
append("eval",string);
_eval(string);
};
var _setTimeout = setTimeout;
window.setTimeout = function(code,millisec) {
append("settimeout",code);
_setTimeout(code,millisec);
};
var _replace = function(url)
{
append("location.replace",url);
};
location.__defineSetter__('href', function(url) {
append("location",url);
});
</script>
0x06 下载地址
0x07 参考
*本文原创作者:lufei,本文属FreeBuf原创奖励计划,未经许可禁止转载