PHP函数缺陷和安全问题
前言
尽可能全面的总结PHP的各种安全问题
基础知识
弱类型及各种函数
伪协议
反序列化
其他
一、基础知识
1、九大全局变量
$_POST:用于接收post提交的数据
$_GET :用于获取url地址栏的参数数据
$_FILES :用于文件接收的处理, img 最常见
$_COOKIE :用于获取与setCookie()中的name 值
$_SESSION :用于存储session的值或获取session中的值
$_REQUEST :具有get、post的功能,但比较慢
$_SERVER:预定义服务器变量的一种
$GLOBALS :一个包含了全部变量的全局组合数组
$_ENV :是一个包含服务器端环境变量的数组。它是PHP中一个超级全局变量,我们可以在PHP 程序的任何地方直接访问它
二、弱类型以及各种函数
1、精度缺陷
在用PHP进行浮点数的运算中,经常会出现一些和预期结果不一样的值,这是由于浮点数的精度有限 尽管取决于系统,PHP 通常使用 IEEE 754 双精度格式,则由于取整而导致的最大相对误差为 1.11e-16 非基本数学运算可能会给出更大误差,并且要考虑到进行复合运算时的误差传递
下面看一个有趣的例子感受下:

以十进制能够精确表示的有理数如 0.1 或 0.7,无论有多少尾数都不能被内部所使用的二进制精确表示 因此不能在不丢失一点点精度的情况下转换为二进制的格式
这就会造成混乱的结果: 例如,floor((0.1+0.7)*10) 通常会返回 7 而不是预期中的 8,因为该结果内部的表示其实是类似 7.9999999999999991118…
2、类型转换缺陷
PHP弱类型语言的一个特性,当一个整形和一个其他类型比较的时候,会先把其他类型intval数字化再比
举个例子
既要传入非数字,又要比2020大
那就传个?id=2021a即可
3、==和===
==和===比较符如下

尤其要关注的是==和===
==会先将字符串类型转换成相同,再比较===会先判断两种字符串的类型是否相等,再比较


一些利用
一批md5开头是0e的字符串
实际利用
4、strcmp()函数
strcmp()函数这是个比较字符串的函数
如果
str1小于str2返回< 0如果
str1大于str2返回> 0如果两者相等,返回 0
问题
在PHP版本为5.3.3至5.5中(不包含5.5),当比较数组和字符串的时候,返回值也是0
例子
绕过
拓展
除了
strcmp()函数外,ereg()和strpos()函数在处理数组的时候也会异常,返回NULL
5、intval()函数
intval()函数用于获取变量的整数值 在转换时,函数会从字符串起始处进行转换直到遇到一个非数字的字符 即使出现无法转换的字符串也不会报错而是返回0
于是有

6、sha1() 和md5()加密函数
sha1() 和md5()加密函数都用于计算字符串的散列值 但是两者都无法处理数组,不会抛出异常而是直接返回NULL
例子
绕过方法
7、parse_str()函数
parse_str()函数解析字符串并注册成变量,在注册变量之前不会验证当前变量是否存在,所以直接覆盖掉已有变量
当parse_str()函数的参数值可以被用户控制时,则存在变量覆盖漏洞
例子

结合弱类型的例子
绕过
8、is_numeric()函数
is_numeric()函数用于检测变量是否为数字或数字字符串
可被十六进制的值进行绕过
例子
1′ union select 1,2,3的十六进制为0x312720756e696f6e2073656c65637420312c322c33
绕过
9、in_array()函数
in_array()函数用来判断一个值是否在某一个数组列表里面 其缺陷在于存在自动类型转换 当输入数字1后再紧跟其他字符串能够Bypass检测数组的功能
例子

10、ereg()和eregi()
ereg()和eregi()用于正则匹配,两者的区别在于是否区分大小写 使用指定的模式搜索一个字符串中指定的字符串,如果匹配成功则返回true,否则返回false
该函数可被%00截断来Bypass
传入数组之后,ereg是返回NULL
例子

11、json_decode()函数
json_decode()函数用于对json格式数据进行json解码操作,对于一个json类型的字符串,会解密成一个数组
其存在一个0=="efeaf"的Bypass
例子

12、preg_match() 函数
preg_match() 函数用于执行一个正则表达式匹配
/i 修饰符
大小写不敏感
绕过
/m 修饰符
多行匹配
当出现换行符 %0a的时候,会被当做两行处理
此时只可以匹配第 1 行,后面的行就会被忽略
绕过
13、preg_replace()函数
preg_replace()函数执行一个正则表达式的搜索和替换
/e修饰符
使 preg_replace() 将 replacement 参数当作 PHP 代码
1、无限传参
绕过
2、简单正则
绕过
然后就可以蚁剑了


3、进阶正则
绕过
14、register_globals全局变量覆盖
register_globals全局变量覆盖php.ini中有一项为register_globals,即注册全局变量
当register_globals=On,变量未被初始化且能够用户所控制时,就会存在变量覆盖漏洞
例子

15、extract()变量覆盖
extract()变量覆盖从数组中将变量导入到当前的符号表 使用数组键名作为变量名,使用数组键值作为变量值 针对数组中的每个元素,将在当前符号表中创建对应的一个变量
第二个参数指定函数将变量导入符号表时的行为
当extract()函数从用户可以控制的数组中导出变量且第二个参数未设置或设置为EXTR_OVERWRITE时,就存在变量覆盖漏洞
例子

16、import_request_variables()变量覆盖
import_request_variables()变量覆盖将GET、POST、Cookies中的变量导入到全局 4.1.0 <= PHP < 5.4.0
例子

17、$$导致的变量覆盖
$$导致的变量覆盖 例子
使用foreach来遍历数组中的值,然后再将获取到的数组键名作为变量,数组中的键值作为变量的值
传入id=mi1k7ea后,在foreach语句中,\$_key为id,\$_value为mi1k7ea,进而\$\$_key为$id,从而实现了变量覆盖

18、strstr()函数
strstr()函数大小写敏感
实例
19、mt_rand()函数
mt_rand()函数随机数生成工具
问题在于每个php cgi进程期间,只有第一次调用mt_rand()会自动播种 接下来都会根据这个第一次播种的种子来生成随机数
所以可以通过逆向得到随机种子 然后获取后面其他随机数 如路径之类的信息就有了
工具
实例
三、伪协议
php伪协议主要有以下
通常都会用在文件包含上
1、php:// 输入输出流
php:// 输入输出流PHP 提供了一些杂项输入/输出(IO)流,允许访问 PHP 的输入输出流、标准输入输出和错误描述符, 内存中、磁盘备份的临时文件流以及可以操作其他读取写入文件资源的过滤器
(1)php://filter
元封装器,设计用于”数据流打开”时的”筛选过滤”应用
本地磁盘文件进行读取
不需要开启
allow_url_fopen和allow_url_include
有一些敏感信息会保存在php文件中,如果我们直接利用文件包含去打开一个php文件,php代码是不会显示在页面上的 这时候我们可以以base64编码的方式读取指定文件的源码
用法

实例
(2)php://input
可以访问请求的原始数据的只读流
即可以直接读取到POST上没有经过解析的原始数据
不需要开启
allow_url_fopen和allow_url_include在遇到
file_get_contents()时可以用php://input绕过

可以用来执行命令

也可以写入木马

2、file:// 读取文件内容
file:// 读取文件内容通过file协议可以访问本地文件系统,读取到文件的内容
且不受allow_url_fopen与allow_url_include的影响
只能输入绝对路径,输入相对路径不生效

注
输入php或JS文件,file://协议会执行该PHP文件里的代码而不是显示该内容
3、data:// 读取文件
data:// 读取文件数据流封装器,和php://相似都是利用了流的概念 将原本的include的文件流重定向到了用户可控制的输入流中 简单来说就是执行文件的包含方法包含了你的输入流
条件
php版本大于等于php5.2
必须同时开启
allow_url_fopen和allow_url_include
使用方法
执行命令

base64绕过

实例
4、phar:// 针对压缩包
phar:// 针对压缩包php解压缩包的一个函数 不管后缀是什么,都会当做压缩包来解压
条件
压缩包需要zip协议压缩
php版本大于等于php5.3.0
用法
一句话木马文件shell.php
用zip协议压缩为shell.zip
将后缀改为png等其他格式
上传
访问

实例
5、zip:// 针对压缩包
zip:// 针对压缩包类似phar:// 使用方法和条件有点区别
条件
压缩包需要zip协议压缩
php版本大于等于php5.3.0,windows下php还得小于5.4
不需要开启
allow_url_fopen和allow_url_include#编码为%23,接上压缩包内的文件需要指定绝对路径

注
类似的还有zlib://协议和bzip2://协议
四、反序列化
php序列化的两个函数
serialize():将一个对象转成字符串形式,方便保存以便于下次再次反序列化出该对象直接使用unserialize():将序列化后的字符串反序列化成一个对象
1、序列化与反序列化
考虑User具有以下属性的对象:
序列化后,该对象可能看起来像这样:
可以解释如下:
2、魔术方法
魔术方法就是在某些条件下自动执行的函数
参考官方文档 一些魔术方法如下
最重要的几个
3、PHP的反序列化漏洞
PHP反序列化漏洞出现的原因:
unserialize()传入参数可控
在某些魔术方法可用
有过滤或者过滤不完善
通过几个例子来感受下
例子1——wakeup
代码中写了
__wakeup()在反序列化之前一定会调用此方法,创建了一个test.php文件
把Test类中的test变量的值写进了test.php文件
require进行文件包含
payload

注:CVE-2016-7124漏洞:序列化字符串表示对象属性个数的值大于真实个数的属性时就会跳过wakeup的执行
实例
例子2——construct
contruct打开了一个shell.php
wakeup调用了Test1类
require文件包含
payload
例子3——destruct
本结束时就会调用destruct函数,同时会覆盖test变量
payload
例子4——session
首先php的session存储与读取是一个序列化跟反序列化的过程,其中有三种模式,分别是php_binary、php、php_serialize,这几个模式的存储方式不太一样,这也是会导致反序列化漏洞的根源
例子5——phar://的应用
hitcon Orange 的一道 0day 题的解法,打开了反序列化的大门,之后再black 2018有一位演讲者也谈到了phar协议在反序列化中的运用,大大增加了攻击面
phar 文件包在 生成时会以序列化的形式存储用户自定义的 meta-data ,配合 phar:// 我们就能在文件系统函数 file_exists()、 is_dir() 等参数可控的情况下实现自动的反序列化操作,于是我们就能通过构造精心设计的 phar 包在没有 unserailize() 的情况下实现反序列化攻击,从而将 PHP 反序列化漏洞的触发条件大大拓宽了,降低了我们 PHP 反序列化的攻击起点
演讲中测试发现php中很多很多函数配合phar协议,可以触发反序列化漏洞。比如说file_get_contents、file_exists、还有最新的SUCTF遇到的finfo_file等等。这里提一下,含可以通过php://filter/resource=伪协议的形式后面跟phar协议,同样可以触发。这是在SUCTF做题中最新发现的一个点
利用条件: 存在文件上传点,还有操作系统文件的函数
跟进phar源码,其实最根源的是底层调用了php_stream_open_warpper_ex函数处理
例子6——pop链
(1)寻找 unserialize() 函数的参数是否有我们的可控点
(2)寻找我们的反序列化的目标,重点寻找 存在 wakeup() 或 destruct() 魔法函数的类
(3)一层一层地研究该类在魔法方法中使用的属性和属性调用的方法,看看是否有可控的属性能实现在当前调用的过程中触发的
五、其他
见到的一些安全问题
1、动态特性
直接放链接 PHP动态特性的捕捉与逃逸
2、webshell免杀
直接放链接 PHP webshell 免杀姿势总结
3、ThinkPHP 5.x远程命令执行漏洞
直接放链接 ThinkPHP 5.x远程命令执行漏洞分析与复现
实例
4、PHP混淆后门
直接放链接 一个PHP混淆后门的分析
实例
5、一些函数的巧用
看题目 攻防世界 web高手进阶区 8分题 love_math
6、数组key溢出
PHP的hastTable是通过链表法实现的,按说是不会存在溢出的问题 但是其索引值表示的范围有限,当超出索引值时就会造成溢出 这个溢出只存在当索引值为数字时,输入的数字为正,输出却为负值的原因是函数参数与输出的类型不一致导致的
看个例子
上面这些输出的结果是
当key值很大时输出的值溢出了,临界点是9223372036854775807这个数字
实例
结语
本文总结归纳PHP的各种安全问题
参考:
Last updated
Was this helpful?