PHP部分函数绕过利用篇

PHP提供了很多强大的函数来简化程序员的开发操作,但部分函数的功能特性有部分缺陷,或函数间搭配不当,导致我们可进行绕过。

弱类型比较绕过

先了解用作参数比较的两个算术运算符:=====
共同点:对运算符两边的参数进行数值比较,数值相等则表达式为True
不同点:==两边的参数类型不相等时,会先对两边的数值类型转换至相同的类型,详细的转换规则可参照浅谈PHP类型转换机制。而===两边的参数类型不相等时则表达式为False,不会继续进行比较。

在看完《浅谈PHP类型转换机制》后,就容易联想到可在多个场景进行弱类型比较绕过。如在验证md5值时使用md5($param)后值以0e开头的$param。对验证参数类型的可构造类型转换后数值”相等”的参数。如:

$x = $_GET['x'];
if(gettype($x) == 'string'){        # 1
    if($x == 123){                     # 2
        # do something
    }
}
------------------------
?x=123a

在第一次比较时,x为字符型。
在第二次比较时,==左边为字符型,右边为整型。进行类型转换,左边按照机制转换为123,此时与右边数值相等,返回True。

字符串解析函数绕过

parse_str()

字符串解析函数,解析字符串并且注册成变量,展示示例:

parse_str("user=admin","password"="123");
-------------------
Array
(
    [user] => admin
    [password] => 123
)

特性:
– 在注册变量之前不会验证当前变量是否存在,所以会直接覆盖掉当前作用域中原有的变量。
– 在把字符串解析成参数时会把开头的空格删掉,并替换掉字符串中的某些字符替换成下划线_

利用点分析:
当PHP中在使用parse_str()前未对函数名称做一遍检查时,可能存在变量覆盖的漏洞。

可利用此缺陷覆盖一些变量,如覆盖include($file);中的$file

利用特殊符号构造特定参数。如:

后台接收参数为:foo_bar
payload:
?%20foo_bar=xxx
?+foo_bar=xxx

?foo%20bar=xxx
?foo.bar=xxx
?foo[bar=xxx
?foo+bar=xxx

?foo_bar%00=xxx

-------------------
等价于:?foo_bar=xxx

在一些WAF机制中会匹配下划线的参数并检测其参数值,可利用该特性来绕过。


parse_url()

URL解析函数,会解析传入的URL并返回URL中已有的各组成部分,如:

$url = 'https://www.keyboy.xyz/index.php?id=1';
$parse_url = parse_url($url);  
print_r($parse_url);  
-----------------------------------
Array
(
    [scheme] => https
    [host] => www.keyboy.xyz
    [path] => index.php
    [query] => id=1
)

多试试几个例子,根据规律猜测函数是基于/:?#这些特殊符号进行匹配切割的。若$url不符合URL规则时将返回False。所以仔细分析的话,若代码是以parse_url($url)[host]这样的形式对每个URL部分进行过滤分析的话,我们可以写入一些特殊的URL来绕过验证。

另外各种环境对URL的解析有差异,也可被我们综合利用。

示例一

url分块验证被绕过,漏洞代码:

分析:不会被`parse_url()`识别为路径分割符,但浏览器会自动统一URL路径中的,因此 http://www.baidu.com/index.phpwww.baidu.comindex.php 在浏览器中打开是完全一致的。在部分场景中,可以被利用来绕过检测,如上图所示,题目本意是不允许访问flag.php,但变形后的URL通过parse_url()提取出来的path是空的,因此绕过了正则验证。

另一种相似的绕过手法:

实际上访问的是http://keyboy.xyz/baidu.com,而我们可以在我们的网站目录下创建一个baidu.com这样的文件夹,这样还将继续访问我们部署在网站下的baidu.com文件夹,可以用来绕过网站限制,实施SSRF攻击。


示例二

parse_url()[‘port’]解析漏洞

对于带端口的URL:https://www.domain.com:{port}{path}
port的合理取值范围为1-65535
但我测试后发现,此处的长度不能超过5,类型需要为整型。但我们知道PHP会自动为参数转型,所以我们可以尝试在此处填写满足[0-9][^0-9]{1,4}规则的字符,这样就可以把后面的非整型数值给抹去了。若是端口号后面还需要附带它参数,则必须以/?#来划清界限,不然会被归入parse_url()['port']的范围内,造成长度报错,返回False(对于整个parse_url()数组均为空)。在示例中,可见我们填塞进去的flag在转型过程中被“吞了”,从而绕过了正则验证。


某些正则绕过方式

组合拳式绕过

以常用的正则函数preg_match($path , $string , $mode)为例,功能是匹配$string中是否有符合$path的字符串。但该函数返回值有三种:

return 0;           // 没有匹配到
return 1;           // 匹配成功时
return False;           // 发生错误时

preg_match($path , $string , $mode)发生错误并返回False的场景:

一:数据类型出错
PHP规定了$path和$string的类型必须为string型(整型自动转型),不可以匹配Array()。因此:

$param = $_GET['param'];
if(isset($param)){
    if(preg_match('[a-zA-Z0-9]' , $param)){
        die('hacker!');
    }
    echo FLAG;
}
--------------------
payload : /?param[]=aaa

由于$param是数组形式的,在正则函数匹配时出错导致返回False,但由于用了if()进行判断,0与False等价,由此可以顺利通过。

可对传入的参数溯源,查找可利用的点,如parse_url()可以让我们控制传入的参数值,在ctf中遇到的一般可用传入数组的形式绕过。

修补方案:对参数进行类型判断。

if(is_array($param))
    exit();

二:未知修饰符
理论上只会出现在开发者身上,漏洞点在$path上。或许代码审计时可注意一下,先看图:

首先处理的都是相同的参数$str,但两种$path写法有很大的差异,$path两边需要用/界限符来标识,如果忘记写了(习惯于写python等其它语言的可能会忘记写)就会导致语句一直出错,然后然后False。
tips:上面某图我的正则中的$path就没有标记修饰符,不过我懒得替换 🙂

观点概述:由于只是简单地使用if(preg_match($path,$string))来判断,导致正则返回0和False没有区分度,所以很多时候都是特意制造错误来绕过正则。所以建议使用区分度更高的写法:

if(preg_match($path,$string)==1){}      // 匹配成功
if(preg_match($path,$string)==0){}      // 匹配失败
if(preg_match($path,$string)==False){}      // 匹配错误

上古函数ereg

在较新的PHP中已不支持ereg()正则库了,因为该函数存在致命的00截断问题,还有数组绕过问题。至于怎么利用,各位老司机可能已经驾轻就熟了。


由于篇幅巨大,后续将另写PHP的MySQL绕过,反序列化绕过等。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据