介绍
该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