Finecms代码审计
1.SQL语句执行
/finecms/dayrui/config/config.php
中的第37行对cookie_name进行了定义。
$config['sess_cookie_name'] = $site['SYS_KEY'].'_ci_session';
这里$site[‘SYS_KEY’]不就把SYS_KEY的值给暴露了。
这个SYS_KEY是安全码,有了它就能进行接下来的敏感函数的调用。
/finecms/dayrui/controllers/Api.php
中的115-166行的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;
}
可以看到开始有个安全码的验证,有了之前得到的安全码,就可以利用这个函数了.
if ($auth != md5(SYS_KEY)) {
将auth值与SYS_KEY的md5值进行比对,同则继续,异则失败。
$param = $this->input->get('param');
获得输入param参数值。
$data = $this->template->list_tag($param);
造成了漏洞。
定位list_tag()函数看看。
/finecms/dayrui/libraries/Template.php
第402-1314是list_tag函数。
一部分代码。
switch ($system['action']) {
·····················
·················
·········
case 'sql': // 直接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 = $this->ci->set_cache_data($name, $data, $system['cache']);
$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;
case 'table': // 表名查询
if (!$system['table']) {
return $this->_return($system['return'], 'table参数不存在');
}
// 默认站点参数
$system['site'] = !$system['site'] ? SITE_ID : $system['site'];
$tableinfo = $this->ci->get_cache('table');
if (!$tableinfo) {
$this->ci->load->model('system_model');
$tableinfo = $this->ci->system_model->cache(); // 表结构缓存
}
if (!$tableinfo) {
return $this->_return($system['return'], '表结构缓存不存在(后台菜单-更新表结构)'); // 没有表结构缓存时返回空
}
$table = $this->ci->db->dbprefix($system['table']); // 主表
if (!isset($tableinfo[$table]['field'])) {
return $this->_return($system['return'], '表('.$table.')结构缓存不存在(后台菜单-更新表结构)');
}
$where = $this->_set_where_field_prefix($where, $tableinfo[$table]['field'], $table); // 给条件字段加上表前缀
$system['field'] = $this->_set_select_field_prefix($system['field'], $tableinfo[$table]['field'], $table); // 给显示字段加上表前缀
$system['order'] = $this->_set_order_field_prefix($system['order'], $tableinfo[$table]['field'], $table); // 给排序字段加上表前缀
$total = 0;
$sql_from = $table; // sql的from子句
就可以执行sql语句了。
POC: