PHP反序列化漏洞
目录
什么是序列化
- 将对象转换成字符串
更简单的存储对象 - 持久保存
- 网络传输
<?php
class Student{
public $name = 'deelmind';
function getName{
return "deelmind";
}
}
$s = new Student();
echo $s->getName()."</br>";// deelmind
$s_serialize = serialize($s);
echo $s_serialize;// O:7:"Student":1:{s:4:"name";s:8:"deelmind";}
echo "</br>";
error_reporting(0);
?>
O:Object
7:此对象长度,即为$s的长度
Student:类的名称
1:对象属性(变量)的个数,常量(const)不会计入
s:str
4:第一个字符串的长度,name这个变量名也为str形式
8:第二个字符串的长度
什么是反序列化
- 字符串转换成对象
- 文件变为内存
$Student = 'O:7:"Student":1:{s:4:"name";s:8:"deelmind";}';
$s_unserialize = unserialize($Student);
echo $s_unserialize;// Student Object([name]=>deelmind)
相关函数
serialize()
unserialize()
魔术方法
反序列化漏洞
绕过__wakeup()
这是很基础的一个漏洞
__wakeup()
unserialize() 执行时会检查是否存在一个 wakeup() 方法。如果存在,则会先调用 wakeup 方法,预先准备对象需要的资源。wakeup()经常用在反序列化操作中,例如重新建立数据库连接,或执行其它初始化操作。
<?php
class A{
public $a = "nice to meet u~";
function __destruct(){
echo "destruct!"."</br>";
}
function __wakeup(){
echo "wakeup!"."</br>";
}
}
$b = new A;
$c = serialize($b);
echo unserialize($c);// wakeup!
// destruct!
?>
绕过__wakeup()
在反序列化的过程中会首先调用__wakeup()方法,然后再调用__destruct()方法。而在一些题目中,会在__wakeup()方法中有阻住访问flag等语句,所以当我们想要得到flag或进行一些注入行为时,就需要绕过__wakeup()方法,直接调用__destruct()方法,从而做到我们想做的事情。
这个漏洞核心:序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行
举个栗子,在上面解释什么是序列化的代码中,我们得到了一个Student对象序列化过后的payload(payload 可以理解为一系列信息中最为关键的信息)
payload:O:7:“Student”:1:{s:4:“name”;s:8:“deelmind”;}
当我们把对象属性个数(:1:),改为比1大的数字(如 :2: ),就可以绕过__wakeup()方法
可以绕过的payload:O:7:“Student”:2:{s:4:“name”;s:8:“deelmind”;}
注意:
- 对protected属性参数构造payload时,需要在参数名前加\00*\00
- \x00+类名+\x00+变量名 反序列化出来是private变量
- 序列化字符串,在对象前面可以加“+”
O:+7:“Student”:1:{s:4:“name”;s:8:“deelmind”;}
常用在有正则匹配的时候
preg_match('/[oc]:\d+:/i',$data,$matches);
贴一个我认为很棒的解释
https://www.freebuf.com/news/172507.html
_toString
只有在echo的时候才会触发这个魔术方法,通过需要echo的值,传入序列化参数,调用此函数,在toString中有时会有get_file_contents()函数从而可以让我们看到我们想看到的文件内容(flag)
魔术方法总结
我们更需要了解的是这个魔术方法在什么时候会被触发,从而在触发时通过序列化参数,再反序列化,而得到我们想要的
phar反序列化
利用phar文件会以序列化的形式储存用户自定义的meta-data这一特性,拓展了php反序列化攻击的攻击面。该方法在文件系统函数(file_exists()、is_dir()等)参数可以被控制的情况下,配合phar://伪协议,可以不依赖unserialize()直接进行反序列化操作。
phar文件结构
- Stub
是phar文件的一个标志,必须以__HALT__COMPILER();?>来结尾,前面可以添加任意内容(有时可以用来伪装phar文件,添加其他类型的文件头来伪装),否则phar扩展将无法识别这个文件是phar文件。 - manifest
phar文件本质是一种压缩文件,在这一部分会以序列化的形式存储用户自定义的meta-data,也是此类攻击的核心。 - the file contents
文件的内容 - signature
签名,在文件末尾
<?php
class TestObject{
}
//生成phar文件
@unlink("phar.phar");
$o = new TestObject();
$phar = new Phar("phar.phar");//后缀必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");//设置stub
$phar->setMetadata($o);//将自定义的meta-data存入manifest
$phar->addFromString("test.txt"."test");//添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>
利用phar://伪协议进行攻击
我们可以通过观察网页的一些后缀,通过下载网页的php文件,观察是否可以使用phar://伪协议攻击,当代码中出现以下文件函数,可使用的可能性就大大增加
通过观察代码写出phar文件,通过GET或POST:http://test.com/?a=phar://<定义的phar文件名称>/<定义的压缩文件名称>
有时代码中会有正则匹配导致无法使用phar://我们可以利用compress.zlib://phar://或者compress.bzip2://phar://来绕过正则
POP链
之前所接触的反序列化攻击多是在魔术方法中出现一些利用的漏洞,自动调用从而触发漏洞。但如果关键代码不在魔术方法中,而是在一个类的普通方法中。这时候可以通过寻找相同的函数名将类的属性和敏感函数的属性联系起来,这也就是POP链的来源。
A->b->c
在这条链中,我们需要控制c的参数,但我们无法改变c方法的参数,只能改变A的参数,这时就需要构造pop链,让我们可以间接控制c的参数。
由于php基础不够牢固,所以这一部分还不熟练
Session反序列化漏洞
PHP内置了多种处理器用于存取$_SESSION 数据时会对数据进行序列化和反序列化,常用的有以下三种,对应三种不同的处理格式
处理器 | 对应的存储格式 |
---|---|
php | 键名+竖线+经过serialize()函数反序列处理的值 |
php_binary | 键名的长度对应的ASCII字符+键名+经过serialize()函数反序列处理的值 |
php_serialize(php>=5.5.4) | 经过serialize()函数反序列处理的数组 |
<?php
ini_set('session.serialize_handler','php');//配置session
session_start();
$_SESSION['a'] = $_GET['a'];
?>
在session的保存路径中就可以看到一个文件,存有序列化之后的值
安全问题
如果php在反序列化存储的$_SESSION数据时使用的处理器和序列化时使用的处理器不同,会导致数据无法正常反序列化,通过特殊的构造,甚至可以伪造任何数据
当配置选项session.auto_start = Off,两个脚本注册Session会话时使用的序列化处理器不同,就会出现安全问题
a:1