导语:在本文中,我们将主要分析如何绕过过滤器、输入清理和WAF规则,实现PHP的远程代码执行。
概述
在本文中,我们将主要分析如何绕过过滤器、输入清理和WAF规则,实现PHP的远程代码执行。通常,当我在写这样的文章时,人们总是会问,“真的有人写出这样的代码吗?”并且通常都不是疑问句。在再次被问到这个问题之前,我要抢先回答:“是真的。”
在研究过程中,我们对两个易受攻击的PHP脚本进行了测试。其中,第一个脚本非常简单,并且近乎愚蠢,但它只是为了重现一个远程代码执行漏洞的利用场景:
显然,第六行存在问题。第三行尝试拦截注入system、exec或passthru之类的函数。在PHP中,有许多其他函数可以执行系统命令,但我们重点关注这三个函数。该脚本在CloudFlare WAF后面的Web服务器中运行。和往常一样,我使用了CloudFlare,因为它非常简单,并且广为人知,并不意味着CloudFlare WAF不安全。其他所有WAF都或多或少会有相同的问题。第二个脚本落后于ModSecurity + OWASP CRS3的安全要求。
尝试读取/etc/passwd
针对第一个脚本,我尝试使用system()函数,通过请求/cfwaf.php?code=system(“cat /etc/passwd”);来读取/etc/passwd。
如大家所见,CloudFlare会阻止我的请求,可能是由于/etc/passwd。但是,我们有方法能轻松绕过,可以使用类似于cat /etc$u/passwd这样的命令。
CloudFlare WAF已经被绕过,但其中还存在对用户输入的检查,这阻止了我的请求,因为我正在尝试使用“system”函数。那么不禁要问,是否有一种语法,能让我在不使用“system”字符串的情况下使用系统功能呢?我们来看看PHP官方文档中,有关字符串的内容:https://secure.php.net/manual/en/language.types.string.php
PHP字符串转义序列
\[0–7]{1,3}是八进制表示法的字符序列,可以溢出一个字节。例如:“\400” === “\000”
\x[0–9A-Fa-f]{1,2}是十六进制表示法的字符序列。例如:“\x41"
\u{[0–9A-Fa-f]+}是Unicode代码点(Codepoint)序列,将作为该代码点的UTF-8表示输出到字符串(在PHP 7.0.0版本中加入)。
看来,并不是所有人都知道PHP中有很多用于表示字符串的语法。因此,使用“PHP变量函数”,就成为了我们绕过过滤器和WAF规则的瑞士军刀。
PHP变量函数
PHP支持变量函数的概念。这意味着,如果变量名称附加了括号,PHP将会查找与变量等价的名称相同的函数,并尝试执行。除此之外,这可以用于实现回调、函数表等功能。
这意味着,像var(args);和“string”(args);这样的内容,都等价于function(args);。如果我能够通过使用变量和字符串来调用函数,那么我就可以使用转义序列,而不再是函数的名称。一个例子如下所示:
其中,第三种语法是十六进制表示法中的字符转义序列,PHP将其转换为字符串“system”,然后使用参数“ls”将其转换为函数system。我们在易受攻击的脚本上进行尝试:
这种技术不适用于所有的PHP函数,变量函数不适用于类似echo、print、unset()、isset()、empty()、include、require这样的语言结构。利用包装器函数,可以将这些结构中的任何一个作为可变函数。
改进用户输入清理
在易受攻击的脚本中,如果我从用户输入中排除双引号和单引号等字符,那么会发生什么?即使不使用双引号,是否也可以绕过它?我们来进行一下尝试:
正如我们在第三行所看到的那样,现在脚本将阻止在$_GET[code] querystring参数中使用双引号和单引号。现在,我之前的Payload应该已经被阻止:
幸运的是,在PHP中,我们并不总是需要引号来表示字符串。PHP中,还存在其他能够声明元素的类型,例如$a = (string)foo;。在这种情况下,$a包含字符串foo。此外,在没有特定类型声明的圆括号中的内容,都将被视为字符串:
基于此,我们有两种绕过新过滤器的方法:第一种方法是使用类似于(system)(ls);的形式,但我们不能再代码参数中使用“system”,所以我们可以连接字符串,类似于(sy.(st).em)(ls);。第二种方法是使用$_GET变量。如果我发送一个请求,类似于?a=system&b=ls&code=$_GET[a]($_GET[b]);,那么其结果为$_GET[a],就会替换为字符串“system”。同时,$_GET[b]可以被替换为字符串“ls”。这样一来,我就能绕过所有过滤器了!
让我们试试第一个Payload:
(sy.(st).em)(whoami);
然后,试试第二个Payload:
?a=system&b=cat+/etc&c=/passwd&code=$_GET[a]($_GET[b].$_GET[c]);
在示例中,这个技巧可能没有帮助,但我们实际上甚至可以在函数名称和参数内插入注释,这将有助于绕过阻止特定PHP函数名称的WAF规则集。以下所有语法都是有效的:
get_defined_functions
该PHP函数将返回一个多维数组,其中包含所有已定义函数的列表,包括内置(内部)函数和用户定义的函数。内部函数可以通过$arr[“internal”]访问,用户定义的函数可以通过$arr[“user”]访问。例如:
这可能是在不使用其名称的前提下,实现system函数的另外一种方法。如果我对“system”进行grep,那么可以发现它的索引号,随后将其用作代码执行的字符串:
显然,这应该适用于我们的CloudFlare WAF和脚本过滤器:
字符数组
PHP中,每个字符串都可以用作字符数组(几乎与Python一样),我们可以使用语法$string[2]或$string[-3]来引用字符串中的单个字符。这可能是另一种逃避阻止PHP函数名称的规则的方法。举例来说,使用字符串$a=”elmsty/ “;,我就可以实现语法system(“ls /tmp”);
如果我们幸运,可以在脚本文件名中找到所需的所有自负。使用相同的技术,我们就可以选择所需的所有字符,类似于(__FILE__)[2]:
OWASP CRS3
不得不说,随着OWASP CRS3的发布,我们的绕过工作变得更难。首先,通过之前所描述的技术,我只能绕过第一关。但是,第一关只是我们在CRS3中能找到的规则的冰山一角,并且第一关旨在防止任何类型的误报。由于其中存在942430号规则“受限制的SQL字符异常检测(args):特殊字符数超出限制范围”,我们的绕过就变得非常困难,实际上所有事情都变得非常困难。我能做的,就是执行一个没有“ls”或“whoami”参数的命令。但是,我并不能像对CloudFlare WAF进行的那样,执行类似于system(“cat /etc/passwd”)的命令:
推荐阅读
Web应用程序防火墙逃避技术 #1
https://medium.com/secjuice/waf-evasion-techniques-718026d693d8
Web应用程序防火墙逃避技术 #2
https://medium.com/secjuice/web-application-firewall-waf-evasion-techniques-2-125995f3e7b0
Web应用程序防火墙逃避技术 #3
https://www.secjuice.com/web-application-firewall-waf-evasion/