PHP反序列化漏洞【三】
来学习下
phar
反序列化漏洞,拓展php反序列化漏洞的攻击面- 特定PHP版本下
__wakeup()
魔术方法绕过漏洞(CVE-2016-7124)
phar
反序列化漏洞
通常我们在利用反序列化漏洞的时候,只能将序列化后的字符串传入unserialize()
,随着代码安全性越来越高,利用难度也越来越大
在2018 Black Hat大会上,安全研究员Sam Thomas分享了议题 It’s a PHP unserialization vulnerability Jim, but not as we know it
,利用phar文件会以序列化的形式存储用户自定义的meta-data这一特性,拓展了php反序列化漏洞的攻击面
该方法在文件系统函数(file_exists()、is_dir()等)参数可控的情况下,配合phar://伪协议,可以不依赖unserialize()直接进行反序列化操作
基本概念
- phar (PHP Archive) 是PHP里类似于Java中jar的一种打包文件,用于归档
- PHP >=5.3 时默认开启支持phar文件
- phar文件默认状态是只读,使用phar文件不需要任何的配置。因此若要创建phar文件需要
php.ini
关闭只读 - phar://伪协议即PHP归档,用来解析phar文件内容
phar文件结构
在了解攻击手法之前先学习下phar的文件结构,通过查阅PHP手册可知一个phar文件有四部分构成:
a stub
可以理解为一个标志,格式为xxx<?php xxx;__HALT_COMPILER();?>
,前面内容不限,但必须以__HALT_COMPILER();
来结尾(?>
可以省略也可以包含),否则phar扩展将无法识别这个文件为phar文件
a manifest describing the contents
phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这里即为反序列化漏洞点
the file contents
被压缩文件的内容
[可选] 验证phar完整性的签名
签名,放在文件末尾,格式如下:
Length in bytes | Description |
---|---|
16 or 20 bytes | 实际签名,SHA1签名为20字节,MD5签名为16字节,SHA256签名为32字节,SHA512签名为64字节。 |
4 bytes | 签名标志. 0x0001 用于表示是 MD5 签名, 0x0002 用来表示是 SHA1 签名, 0x0004 用来表示是SHA256签名, 0x0008用来表示是SHA512签名。 API版本1.1.0引入了SHA256和SHA512签名支持。 |
4 bytes | Magic GBMB 用于定义签名的存在 |
构建phar文件
根据文件结构,自己构建一个phar文件,php内置了一个Phar类来处理相关操作
*注意:要将
php.ini
中的phar.readonly
选项设置为Off,否则无法生成phar文件
// phar.php
<?php
// 定义类
class TestObject {
}
@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
//__HALT_COMPILER(); 结尾也是可以的
$o = new TestObject();
$phar->setMetadata($o); //将自定义的meta-data即对象存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>
执行phar.php
,同目录下生成phar.phar
文件,使用010 Editor
打开文件可以清晰看到phar文件的结构,其中meta-data是以序列化的形式存储的:
有序列化数据必然会有反序列化操作,php一大部分的文件系统函数在通过phar://
伪协议解析phar文件时,都会将meta-data进行反序列化,测试后受影响的函数如下:(引用seaii师傅整理图)
通过一个小demo证明一下
//phar_test.php
<?php
class TestObject