一、概述
WellCMS是一款开源、倾向移动端的轻量级CMS,高负载CMS,亿万级CMS,是大数据量、高并发访问网站最佳选择的轻CMS。登陆该CMS后台,某图片上传处,由于上传文件类型可控,可修改上传文件类型获取webshell。
二、复现
这个漏洞来自一次偶然的测试,一次幸运的测试,那就直接写出我的测试过程。
第一步,登陆该CMS后台:
第二步,进入“后台管理“,定位利用点,点击下图红框中图片进行上传:
上传并抓取数据包:
第三步,修改post包中“filetype”参数类型为“php”;经分析 “data”参数为base64加密,这里我们将测试数据“<?php phpinfo();?>”经过base64加密等构造,形成“data”参数的数据:data%3Aimage%2Fjpeg%3Bbase64%2CPD9waHAgcGhwaW5mbygpOz8%2B,最后数据包放行,返回成功上传为php文件的路径:
最后,尝试访问,成功:
三、大致分析
根据漏洞定位代码文件:route/attach.php,代码如下:
if ($action == 'create') {
// hook attach_create_start.php
user_login_check();
// hook attach_create_check_after.php
$backstage = param(2, 0);
$width = param('width', 0);
$height = param('height', 0);
$is_image = param('is_image', 0); // 图片
$name = param('name');
$data = param_base64('data');
$mode = param('mode', 0); // 上传类型 1主图
$filetype = param('filetype'); // 压缩图片后缀jpeg jpg png等
$convert = param('convert', 0); // 图片转换压缩 = 1
$n = param('n', 0); // 对应主图赋值
$type = param('type', 0); // type = 0则按照SESSION数组附件数量统计,type = 1则按照传入的n数值
// hook attach_create_before.php
// 允许的文件后缀名
//$types = include _include(APP_PATH.'conf/attach.conf.php');
//$allowtypes = $types['all'];
empty($group['allowattach']) AND $gid != 1 AND message(2, '您无权上传');
// hook attach_create_center.php
empty($data) AND message(1, lang('data_is_empty'));
//$data = base64_decode_file_data($data);
$size = strlen($data);
$size > 20480000 AND message(1, lang('filesize_too_large', array('maxsize' => '20M', 'size' => $size)));
// hook attach_create_file_ext_start.php
// 获取文件后缀名 111.php.shtmll
$ext = file_ext($name, 7);
$filetypes = include APP_PATH . 'conf/attach.conf.php';
// hook attach_create_file_ext_before.php
//主图必须为图片
if ($is_image == 1 && $mode == 1 && !in_array($ext, $filetypes['image'])) message(1, lang('well_up_picture_error'));
// hook attach_create_file_ext_center.php
// 如果文件后缀不在规定范围内 改变后缀名
//!in_array($ext, $filetypes['all']) AND $ext = '_' . $ext;
if (!in_array($ext, $filetypes['all'])) {
$ext = '_' . $ext;
} else {
// CMS上传图片
$t == 1 AND $convert == 1 AND $is_image == 1 AND $ext = $filetype;
}
// hook attach_create_file_ext_after.php
$tmpanme = $uid . '_' . xn_rand(15) . '.' . $ext;
// hook attach_create_tmpanme_after.php
$tmpfile = $conf['upload_path'] . 'tmp/' . $tmpanme;
// hook attach_create_tmpfile_after.php
$tmpurl = $conf['upload_url'] . 'tmp/' . $tmpanme;
// hook attach_create_tmpurl_after.php
$filetype = attach_type($name, $filetypes);
// hook attach_create_save_before.php
file_put_contents($tmpfile, $data) OR message(1, lang('write_to_file_failed'));
// hook attach_create_save_after.php
// 保存到 session,发帖成功以后,关联到帖子。
// save attach information to session, associate to post after create thread.
// 抛弃之前的 $_SESSION 数据,重新启动 session,降低 session 并发写入的问题
// Discard the previous $_SESSION data, restart the session, reduce the problem of concurrent session write
sess_restart();
empty($t) AND empty($_SESSION['tmp_files']) AND $_SESSION['tmp_files'] = array();
$t == 1 AND empty($_SESSION['tmp_website_files']) AND $_SESSION['tmp_website_files'] = array();
// hook attach_create_after.php
// type = 0则按照SESSION数组附件数量统计,type = 1则按照传入的n数值
empty($type) AND $n = ($t == 1) ? count($_SESSION['tmp_website_files']) : count($_SESSION['tmp_files']);
$filesize = filesize($tmpfile);
$attach = array(
'backstage' => $backstage, // 0前台 1后台
'url' => $backstage ? '../' . $tmpurl : '' . $tmpurl,
'path' => $tmpfile,
'orgfilename' => $name,
'filetype' => $filetype,
'filesize' => $filesize,
'width' => $width,
'height' => $height,
'isimage' => $is_image,
'downloads' => 0,
'aid' => '_' . $n
);
// hook attach_create_array_after.php
if ($mode == 1) {
// hook attach_create_thumbnail_beofre.php
$_SESSION['tmp_thumbnail'] = $attach;
// hook attach_create_thumbnail_after.php
} else {
// hook attach_create_website_files_beofre.php
// 0 BBS 1 CMS
$t == 1 ? $_SESSION['tmp_website_files'][$n] = $attach : $_SESSION['tmp_files'][$n] = $attach;
// hook attach_create_website_files_after.php
}
// hook attach_create_session_after.php
unset($attach['path']);
// hook attach_create_end.php
message(0, $attach);
}
大致流程:
1、 首先,接受相关参数,将filetype自行设置成“php”:
$data = param_base64('data');
$filetype = param('filetype'); /
2、 进行逻辑判断:
if (!in_array($ext, $filetypes['all'])) {
$ext = '_' . $ext;
} else {
// CMS上传图片
$t == 1 AND $convert == 1 AND $is_image == 1 AND $ext = $filetype;
}
3、 最后成功写入:
$tmpanme = $uid . '_' . xn_rand(15) . '.' . $ext;
// hook attach_create_tmpanme_after.php
$tmpfile = $conf['upload_path'] . 'tmp/' . $tmpanme;
// hook attach_create_tmpfile_after.php
$tmpurl = $conf['upload_url'] . 'tmp/' . $tmpanme;
// hook attach_create_tmpurl_after.php
$filetype = attach_type($name, $filetypes);
// hook attach_create_save_before.php
file_put_contents($tmpfile, $data) OR message(1, lang('write_to_file_failed'));