漏洞描述
xiaocms后台文章发布存在缩略图上传. 该处并没有对文件类型进行校验. 导致可以上传任意文件, 直接getshell.
漏洞分析
通过xiaocms后台的文件上传URL可以得知URL是c=uploadfile&a=uploadify_upload&type=gif,jpg,jpeg,png
根据core/xiaocms.php中的parse_request()
可以知道c参数是控制器,a参数是方法名, 可以定位到admin/contronller/uploadfile.php
而后根据a参数找到uploadify_upload方法定义的位置:
- /**
- * uploadify_upload
- */
- public function uploadify_uploadAction() {
- $type = $this->get('type');
- $size = (int)$this->get('size');
- if ($this->post('submit')) {
- $data = $this->upload('file', explode(',', $type), $size);
- if ($data['result']) echo $data['path'];
- }
- }
可以看见实际的文件上传动作是由$this->upload完成的, 对$this->upload传入了三个参数, 一个字符串常量'file' , 一个$type参数从URL中获取的, 然后一个文件大小$size, 其中$type是我们可以控制的, 这里没有对$type做任何处理.
我们继续跟入$this->upload看看它的实现:
- /**
- * 文件上传
- */
- private function upload($fields, $type, $size) {
- $upload = xiaocms::load_class('upload');
- $ext = strtolower(substr(strrchr($_FILES[$fields]['name'], '.'), 1));
- if (in_array($ext, array('jpg','jpeg','bmp','png','gif'))) {
- $dir = 'image';
- } else {
- $dir = 'file';
- }
- $path = $this->dir .$dir . '/' . date('Ym') . '/';
- if (!is_dir(XIAOCMS_PATH.$path)) mkdirs(XIAOCMS_PATH.$path);
- $file = $_FILES[$fields]['name'];
- $filename = md5(time() . $_FILES[$fields]['name']) . '.' . $ext;
- $filenpath = $path.$filename;
- $result = $upload->set_limit_size(1024*1024*$size)->set_limit_type($type)->upload($_FILES[$fields],XIAOCMS_PATH.$filenpath);
- if (in_array($ext, array('jpg', 'gif', 'png', 'bmp'))) {
- $this->watermark(XIAOCMS_PATH.$filenpath);
- }
- return array('result'=>$result, 'path'=> SITE_PATH . $filenpath, 'file'=>$file , 'ext'=>$dir=='image' ? 1 : $ext);
- }
upload方法做了几个事情:
1. load_class 加载upload类
2. 从$_FILES中取出上传的文件名, 并且以.作为分隔符, 取出文件后缀.
3. 判断该上传文件是属于什么类型的. 图片类型,还是文件类型.
4. 然后建立上传目录, 重命名上传文件, 用上传文件名的后缀拼接重命名之后的文件名.
5. 设置上传文件大小, 设置上传文件类型, 然后调用upload方法上传文件, 对upload方法传入文件原始名称,和重命名之后的文件名称(路径)
6. 如果文件类型是图片就加上水印
7. 返回文件路径
我们先去看看upload方法长什么样子, upload方法在core/library/upload.class.php
- public function upload($file_upload, $file_name)
- {
- if (!is_array($file_upload) || empty($file_name)) return false;
- $this->parse_init($file_upload);
- if (!@move_uploaded_file($this->file_name['tmp_name'], $file_name)) return '文件上传失败,请检查服务器目录权限';
- return true;
- }
可以看见
upload方法在正式
@move_uploaded_file
之前, 调用了
parse_init()方法, 到现在为止,我们还没看见上传文件限制在哪里, 去看看
parse_init:
- protected function parse_init($file)
- {
- $this->file_name = $file;
- if ($this->file_name['size'] > $this->limit_size) {
- echo '您上传的文件:' . $this->file_name['name'] . ' 大小超出上传限制!';
- exit();
- }
- if ($this->limit_type) {
- if (!in_array($this->get_file_ext(), $this->limit_type)) {
- echo '您上传的:' . $this->file_name['name'] . ' 文件格式不正确!';
- exit();
- }
- }
- return true;
- }
在这里我们看见了对上传文件的限制了. 在第24行到第28行. 可以看见它是根据
$this->limit_type来进行判断的.
如果
$this->get_file_ext的后缀不在
this->limit_type中, 就返回格式不正确.直接
exit()
掉, 就无法进入下一步的
@move_uploaded_file
- public function set_limit_type($type)
- {
- if (!$type || !is_array($type)) return false;
- $this->limit_type = $type;
- return $this;
- }
$this->limit-type 在set_limit_type方法中定义, set_limit_size方法要求参数是$type. 通过联系上下文, 我们可以知道$type是直接从URL中获取的. 因此,$this->limit-type 我们是可控的. 可以上传任意文件.
漏洞利用
1. 登陆后台. 选择产品信息发布
2. 在缩略图上传处,选择文件上传. 载入1.php
3. 通过burp拦截请求, 在type后面加入,php. 上传成功, 获得返回地址
转载地址:http://eleveni386.7axu.com/blog/post/admin/xiaocms