php代码审计之YIXUNCMS2.0

介绍

该cms主要使用php5、mysql。建议使用xampp的集成环境。

url路由分析

从几个入口发现,都会直接包含php/index.php这个文件,所以从这个文件开始分析。前面几行做一个预处理和常量的定义,然后包含一些文件。一直到__autoload函数,用于自动加载类。然后解析url是从Prourl:parseurl()函数开始。我们跳转进入看看。

<?php	
	header("Content-Type:text/html;charset=utf-8");  //设置系统的输出字符为utf-8
	date_default_timezone_set("PRC");    		 //设置时区(中国)

	//PHP程序所有需要的路径,都使用相对路径
	define("BROPHP_PATH", str_replace("\\", "/", dirname(__FILE__)).'/');     //BroPHP框架的路径

	define("APP_PATH", rtrim(APP,'/').'/');            //用户项目的应用路径
	define("PROJECT_PATH", dirname(BROPHP_PATH).'/');  //项目的根路径,也就是框架所在的目录
	define("TMPPATH", str_replace(array(".", "/"), "_", ltrim($_SERVER["SCRIPT_NAME"], '/'))."/");
	
	//包含系统配置文件
	$config=PROJECT_PATH."config.inc.php";
	if(file_exists($config)){
		include $config;
	}
	//设置Debug模式
	if(defined("DEBUG") && DEBUG==1){
		$GLOBALS["debug"]=1;                 //初例化开启debug
		error_reporting(E_ALL ^ E_NOTICE);   //输出除了注意的所有错误报告
		include BROPHP_PATH."bases/debug.class.php";  //包含debug类
		Debug::start();                               //开启脚本计算时间
		set_error_handler(array("Debug", 'Catcher')); //设置捕获系统异常
	}else{
		ini_set('display_errors', 'Off'); 		//屏蔽错误输出
		ini_set('log_errors', 'On');             	//开启错误日志,将错误报告写入到日志中
		ini_set('error_log', PROJECT_PATH.'runtime/error_log'); //指定错误日志文件

	}
	//包含框架中的函数库文件
	include BROPHP_PATH.'commons/functions.inc.php';
	

	//包含全局的函数库文件,用户可以自己定义函数在这个文件中
	$funfile=PROJECT_PATH."commons/functions.inc.php";
	if(file_exists($funfile))
		include $funfile;

	
	//设置包含目录(类所在的全部目录),  PATH_SEPARATOR 分隔符号 Linux(:) Windows(;)
	$include_path=get_include_path();                         //原基目录
	$include_path.=PATH_SEPARATOR.BROPHP_PATH."bases/";       //框架中基类所在的目录
	$include_path.=PATH_SEPARATOR.BROPHP_PATH."classes/" ;    //框架中扩展类的目录
	$include_path.=PATH_SEPARATOR.BROPHP_PATH."libs/" ;       //模板Smarty所在的目录
	$include_path.=PATH_SEPARATOR.BROPHP_PATH."libs/sysplugins/";
	$include_path.=PATH_SEPARATOR.PROJECT_PATH."classes/";    //项目中用的到的工具类
	$controlerpath=PROJECT_PATH."runtime/controls/".TMPPATH;  //生成控制器所在的路径
	$include_path.=PATH_SEPARATOR.$controlerpath;             //当前应用的控制类所在的目录 

	//设置include包含文件所在的所有目录	
	set_include_path($include_path);

	//自动加载类 	
	function __autoload($className){
		if($className=="memcache"){        //如果是系统的Memcache类则不包含
			return;
		}else if($className=="Smarty"){    //如果类名是Smarty类,则直接包含
			include "Smarty.class.php";
		}elseif(file_exists(BROPHP_PATH."libs/sysplugins/".strtolower($className).".php")){
			//判断是否是Smarty3内部类,若是就导入
			include strtolower($className).".php";	
		}else{                             //如果是其他类,将类名转为小写
			include strtolower($className).".class.php";	
		}
		Debug::addmsg("<b> $className </b>类", 1);  //在debug中显示自动包含的类
	}

	//判断是否开启了页面静态化缓存
	if(CSTART==0){
		Debug::addmsg("<font color='red'>没有开启页面缓存!</font>(但可以使用)"); 
	}else{
		Debug::addmsg("开启页面缓存,实现页面静态化!"); 
	}
	
	//启用memcache缓存
	if(!empty($memServers)){
		//判断memcache扩展是否安装
		if(extension_loaded("memcache")){
			$mem=new MemcacheModel($memServers);
			//判断Memcache服务器是否有异常
			if(!$mem->mem_connect_error()){
				Debug::addmsg("<font color='red'>连接memcache服务器失败,请检查!</font>"); //debug
			}else{
				define("USEMEM",true);
				Debug::addmsg("启用了Memcache");
			}
		}else{
			Debug::addmsg("<font color='red'>PHP没有安装memcache扩展模块,请先安装!</font>"); //debug
		}	
	}else{
		Debug::addmsg("<font color='red'>没有使用Memcache</font>(为程序的运行速度,建议使用Memcache)");  //debug
	}

	//如果Memcach开启,设置将Session信息保存在Memcache服务器中
	if(defined("USEMEM")){
		MemSession::start($mem->getMem());
		Debug::addmsg("开启会话Session (使用Memcache保存会话信息)"); //debug

	}else{
		session_start();
		Debug::addmsg("<font color='red'>开启会话Session </font>(但没有使用Memcache,开启Memcache后自动使用)"); //debug
	}
	Debug::addmsg("会话ID:".session_id());
	
	Structure::create();   //初使化时,创建项目的目录结构
	Prourl::parseUrl();    //解析处理URL 

	//模板文件中所有要的路径,html\css\javascript\image\link等中用到的路径,从WEB服务器的文档根开始

	
	//$spath=rtrim(substr(dirname(str_replace("\\", '/', dirname(__FILE__))), strlen(rtrim(dirname(__FILE__),"/\\"))), '/\\');
	$spath=rtrim(substr(dirname(str_replace("\\", '/', dirname(__FILE__))), strlen(rtrim($_SERVER["DOCUMENT_ROOT"],"/\\"))), '/\\');
	$GLOBALS["root"]=$spath.'/'; //Web服务器根到项目的根
	$GLOBALS["public"]=$GLOBALS["root"].'public/';        //项目的全局资源目录
	$GLOBALS["res"]=rtrim(dirname($_SERVER["SCRIPT_NAME"]),"/\\").'/'.ltrim(APP_PATH, './')."views/".TPLSTYLE."/resource/"; //当前应用模板的资源

	$GLOBALS["app"]=$_SERVER["SCRIPT_NAME"].'/';           	//当前应用脚本文件
	$GLOBALS["url"]=$GLOBALS["app"].$_GET["m"].'/';       //访问到当前模块

	define("B_ROOT", rtrim($GLOBALS["root"], '/'));
	define("B_PUBLIC", rtrim($GLOBALS["public"], '/'));
	define("B_APP", rtrim($GLOBALS["app"], '/'));
	define("B_URL", rtrim($GLOBALS["url"], '/'));
	define("B_RES", rtrim($GLOBALS["res"], '/'));


	//控制器类所在的路径
	$srccontrolerfile=APP_PATH."controls/".strtolower($_GET["m"]).".class.php";

	Debug::addmsg("当前访问的控制器类在项目应用目录下的: <b>$srccontrolerfile</b> 文件!");
	//控制器类的创建
	if(file_exists($srccontrolerfile)){
		Structure::commoncontroler(APP_PATH."controls/",$controlerpath);
		Structure::controler($srccontrolerfile, $controlerpath, $_GET["m"]); 

		$className=ucfirst($_GET["m"])."Action";
		
		$controler=new $className();
		$controler->run();

	}else{
		Debug::addmsg("<font color='red'>对不起!你访问的模块不存在,应该在".APP_PATH."controls目录下创建文件名为".strtolower($_GET["m"]).".class.php的文件,声明一个类名为".ucfirst($_GET["m"])."的类!</font>");
		
	}
	//设置输出Debug模式的信息
	if(defined("DEBUG") && DEBUG==1 && $GLOBALS["debug"]==1){
		Debug::stop();
		echo Debug::message();
	}



可以分析得知,是通过/index/index/的形式,来调用相应的代码。所以我们的路由解析就算差不多了。

<?php

	class Prourl {
		/**
		 * URL路由,转为PATHINFO的格式
		 */ 
		static function parseUrl(){
			if (isset($_SERVER['PATH_INFO'])){
      			 	//获取 pathinfo
				$pathinfo = explode('/', trim($_SERVER['PATH_INFO'], "/"));
			
       				// 获取 control
       				$_GET['m'] = (!empty($pathinfo[0]) ? $pathinfo[0] : 'index');

       				array_shift($pathinfo); //将数组开头的单元移出数组 
      				
			       	// 获取 action
       				$_GET['a'] = (!empty($pathinfo[0]) ? $pathinfo[0] : 'index');
				array_shift($pathinfo); //再将将数组开头的单元移出数组 

				for($i=0; $i<count($pathinfo); $i+=2){
					$_GET[$pathinfo[$i]]=$pathinfo[$i+1];
				}
			
			}else{	
				$_GET["m"]= (!empty($_GET['m']) ? $_GET['m']: 'index');    //默认是index模块
				$_GET["a"]= (!empty($_GET['a']) ? $_GET['a'] : 'index');   //默认是index动作
	
				if($_SERVER["QUERY_STRING"]){
					$m=$_GET["m"];
					unset($_GET["m"]);  //去除数组中的m
					$a=$_GET["a"];
					unset($_GET["a"]);  //去除数组中的a
					$query=http_build_query($_GET);   //形成0=foo&1=bar&2=baz&3=boom&cow=milk格式
					//组成新的URL
					$url=$_SERVER["SCRIPT_NAME"]."/{$m}/{$a}/".str_replace(array("&","="), "/", $query);
					header("Location:".$url);
				}	
			}
		}
	}

漏洞分析

任意文件删除

通过rips发现,在admin的controls中,有个flink.class.php文件。其中update存在unlink导致的任意文件删除。
在这里插入图片描述
我们定位到指定函数中查看, 这里从logo函数中直接传入文件名字,这里这需要logoc参数存在即可执行unlink函数。所以我们这里直接构造payload。

	function update(){
 			$flink = D('flink');
			if(isset($_POST['logoc'])){
				$logo = $flink->downlogo($_POST['logoc']);
				$srclogo = PROJECT_PATH."public/uploads/logos/".$_POST["logo"];
				if(file_exists($srclogo))
					unlink($srclogo);
			}else{
				$logo = $_POST["logo"];
			}
			if($logo){
				$_POST["logo"] = $logo;
				if($flink->update($_POST,1,1)){
					$this->redirect("index");
				}else{
					$mess = $flink->getMsg();
					if($mess == "")
						$mess = "您未做任何修改";
					$this->mess($mess,false);
					$this->assign("post",$_POST);
				}
			}else{
				$this->mess("LOGO下载失败,请检查URL地址是否正确",false);
				$this->assign("post",$_POST);
			}
			$this->display("mod"); 
		}

在这里插入图片描述

在这里插入图片描述

任意文件写入

在admin\models\flink.class.php文件中,这里file_put_contents函数的$data参数直接从$logour获取而来。file_get_content可以从远程主机获取字符串。$location的位置可以直接访问执行。

		function downlogo($logourl){
			$url = parse_url($logourl); //這裡整理為可用的url
			$logoname = str_replace(".","_",$url['host']).".".array_pop(explode(".",basename($logourl)));
			$path = PROJECT_PATH."public/uploads/logos/";
			if(!file_exists($path)){
				mkdir($path);
			}
			$location = $path.$logoname;
			$data = file_get_contents($logourl);   //file_get_contents函數可從網絡中獲取文件。
			if(strlen($data) > 0){
				file_put_contents($location,$data);    //location路徑是public/uploads/logos中,可以直接訪問。
				return $logoname;
			}else{
				return false;
			}
		}
	}

于是搜索哪里调用了该函数。发现也是在flink类里的insert中。

function insert(){
			$flink = D('flink');
			$_POST['dtime'] = time();
			$logo = $flink->downlogo($_POST['logo']);//下载LOGO并返回LOGO名称
			if($logo){
				$_POST['logo'] = $logo;
				if($flink->insert($_POST,1,1)){
					$this->mess('友情链接添加成功',true);
				}else{
					$this->mess($flink->getMsg(),false);
					$this->assign('post',$_POST);
				}
			}else{
				$this->mess('LOGO下载失败,请检查URL地址是否正确',false);
				$this->assign('post',$_POST);
			}
			$this->display('add');
		}

构造payload使用。
在这里插入图片描述
在这里插入图片描述

任意文件写入

继续在项目中搜索file_put_contentsh函数,发现在baseset.class.php文件中的writeindex函数汇总使用该函数。直接修改/index.php页面的TPLSTYLE和CSTART这两个参数。

static function writeindex($style,$start){
			$file=PROJECT_PATH."index.php";
			$content=file_get_contents($file);
			$reg[]="/define\(\"TPLSTYLE\".+?;/i";
			$reg[]="/define\(\"CSTART\".+?;/i";
			$rep[]="define(\"TPLSTYLE\",\"{$style}\");";
			$rep[]="define(\"CSTART\",\"{$start}\");";
			file_put_contents($file, preg_replace($reg, $rep, $content));
		}

我们全局搜索哪初使用了writeindex函数。发现在writeConfig函数中,使用$post参数传入,于是继续全局搜索writeConfig函数调用情况。
在这里插入图片描述

在admin/controls/base.class.php文件中使用了writeConfig。分析下参数传递链,是通过$_POST->($post[‘appStyle’],$post[‘cstart’])->($style,$start)这样的形式传递的。所以我们构造payload,传递appStyle和cstart这两个参数。使得恶意代码执行。
在这里插入图片描述
因此成功执行代码。
在这里插入图片描述
在这里插入图片描述
写入一句话木马,然后菜刀连接。
在这里插入图片描述
在这里插入图片描述

资料

YIXUNCMS2.0:
链接:https://pan.baidu.com/s/17G5CcxVkcwrUq65Zk2JqVQ 密码:ydwg

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值