pop链的学习到thinkphp5.x反序列化
pop链的学习到thinkphp5.x反序列化
前言
第一次接触序列化的时候还是ctf,没有清晰的思路,今天复现thinkphp反序列化漏洞时看到一篇文章结合代码审计感觉对我用处很大,就记下来。
起点
也就是我们需要找到自动调用的方法,即魔术方法
- __ destruct
- __ wakeup
因为也是第一次真正意义上学习如何构造pop链, __ wakeup()方法ctf里挺常见的,审计的时候还是__ destruct里用得多,就拿thinkphp反序列化来说吧,起点就是
thinkphp/library/think/process/pipes/windows.php
public function __destruct()
{
$this->close();
$this->removeFiles();
}
这里__ destruct()魔术方法自动调用了本类中的两个函数,也就是开始构造pop链的起点,close()方法用处不大,跟进removeFile()方法
private function removeFiles()
{
foreach ($this->files as $filename) {
if (file_exists($filename)) {
@unlink($filename);
}
}
$this->files = [];
}
此函数的作用是删除临时文件,其中filename变量是我们可控的,而且file_exists函数是操作字符串的函数,两个条件都有了,接下来就是寻找跳板的问题
跳板
所谓的跳板,就是在方法和方法、结构和结构、方法和结构之间的跳跃。
然后就会涉及到一些魔法方法和回调方法,比如
__ call()方法,此方法是在类外调用一个类中不可访问的方法时,就会触发__ call()方法,从而可能能利用到的函数
还有call_user_func($this->test)或者call_user_func_array((new test), "aaa")也可以当作跳板
以及new $test($test1$, $test2)可以调用__construct()方法
最后就是等下要涉及到的__ tostring魔法方法,当遇到一些处理字符串的函数时,并且变量我们可控时,可以将其赋值为存在此方法的对象即能调用此方法。
__ tostring作跳板
thinkphp/library/think/model.php
public function __toString()
{
return $this->toJson();
}
可以发现在model.php中存在此方法,并且可以进一步利用,调用tojson方法----进而调用toarray方法
__ call方法作跳板
toarray()方法中最后一段代码涉及到对象的引用,并且该变量我们可控
if (method_exists($this, $relation)) {
$modelRelation = $this->$relation();
$value = $this->getRelationData($modelRelation);
if (method_exists($modelRelation, 'getBindAttr')) {
$bindAttr = $modelRelation->getBindAttr();
if ($bindAttr) {
foreach ($bindAttr as $key => $attr) {
$key = is_numeric($key) ? $attr : $key;
if (isset($this->data[$key])) {
throw new Exception('bind attr has exists:' . $key);
} else {
$item[$key] = $value ? $value->getAttr($attr) : null;//value变量可控
上层代码我就不分析了,和__ tostring的利用方式类似,赋值给value一个不存在getAttr方法的对象,并且有机会利用
这里利用output类中的方法
thinkphp/library/think/console/output.php
public function __call($method, $args)
{
if (in_array($method, $this->styles)) {
array_unshift($args, $method);
return call_user_func_array([$this, 'block'], $args);
}
if ($this->handle && method_exists($this->handle, $method)) {
return call_user_func_array([$this->handle, $method], $args);
} else {
throw new Exception('method not exists:' . __CLASS__ . '->' . $method);
}
}
}
这里回调方法调用本类中的block()方法,进去康康:
protected function block($style, $message)
{
$this->writeln("<{$style}>{$message}</$style>");
}
我有预感又是一系列的调用过程,然后最后会到write()方法中
public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL)
{
$this->handle->write($messages, $newline, $type);
}
在我们进行序列化时$this->handle是我们可控的,可以找外部类中存在write方法的类,可以进行全局搜索这里我们找到的是think/session/driver/memcached.php
public function write($sessID, $sessData)
{
return $this->handler->set($this->config['session_name'] . $sessID, $sessData, $this->config['expire']);
}
好像灭有啥,这里我们还是可控的同样的方式找set方法/think/cache/driver/file.php
public function set($name, $value, $expire = null)
{
if (is_null($expire)) {
$expire = $this->options['expire'];
}
if ($expire instanceof \DateTime) {
$expire = $expire->getTimestamp() - time();
}
$filename = $this->getCacheKey($name, true);
if ($this->tag && !is_file($filename)) {
$first = true;
}
$data = serialize($value);
if ($this->options['data_compress'] && function_exists('gzcompress')) {
//数据压缩
$data = gzcompress($data, 3);
}
$data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
$result = file_put_contents($filename, $data);
if ($result) {
isset($first) && $this->setTagItem($filename);
clearstatcache();
return true;
} else {
return false;
}
好了到这里就可以写入文件了,并且这里参数都是可控的。
终点
终点当然就是利用危险函数进行写shell或者RCE咯
https://blog.csdn.net/qq_43571759/article/details/105919851
这是部分PHP危险函数的总结
大致就到这里,其实还准备了thinphp5.1的漏洞分析,调用过程也差不太多,后半段和thinkphp5.1的RCE利用链差不多,就不写了。
最后
也只是学到一定皮毛,还会继续加油。刚刚并没有说完,没有说咋去构造poc,还有最后写文件的地方filename利用为协议绕过exit,$data值比较棘手,最后是通过文件名写入shell的
参考文章:
https://www.anquanke.com/post/id/196364#h2-3
https://xz.aliyun.com/t/8082

浙公网安备 33010602011771号