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的解析有差异,也可被我们综合利用。
示例一
分析:不会被`parse_url()`识别为路径分割符,但浏览器会自动统一URL路径中的
,因此 http://www.baidu.com/index.php
与 www.baidu.comindex.php
在浏览器中打开是完全一致的。在部分场景中,可以被利用来绕过检测,如上图所示,题目本意是不允许访问flag.php,但变形后的URL通过parse_url()提取出来的path
是空的,因此绕过了正则验证。
另一种相似的绕过手法:
实际上访问的是http://keyboy.xyz/baidu.com
,而我们可以在我们的网站目录下创建一个baidu.com
这样的文件夹,这样还将继续访问我们部署在网站下的baidu.com
文件夹,可以用来绕过网站限制,实施SSRF攻击。
示例二
对于带端口的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绕过,反序列化绕过等。