0x00 CMS核心目录
网站使用框架:Thinkphp App 应用目录 data 网站上传配置、编辑器目录 include 网站配置调式目录 install 安装目录 public 公共目录 uploads 上传目录
|
环境:
CMS:XYHCMS V3.5
PHP:5.6
MYSQL:5.7.26
0x01 后台任意文件上传
漏洞利用
登录后台,有一个可修改网站配置的选项
当在允许附件类型或是允许图片类型处添加php后,可直接上传webshell获取权限。
漏洞分析
网站设置——>App/controller/SystemController.class.php/194行 site函数
public function site() { if (IS_POST) {
$data = I('config', array(), 'trim'); foreach ($data as $key => $val) { if (stripos($val, '<?php') !== false) { $data[$key] = preg_replace('/<\?php(.+?)\?>/i', '', $val); } }
$data['CFG_IMGTHUMB_SIZE'] = strtoupper($data['CFG_IMGTHUMB_SIZE']); $data['CFG_IMGTHUMB_SIZE'] = str_replace(array(',', 'X'), array(',', 'X'), $data['CFG_IMGTHUMB_SIZE']); if (empty($data['CFG_IMGTHUMB_SIZE'])) { $this->error('缩略图组尺寸不能为空'); }
if (!empty($data['CFG_IMAGE_WATER_FILE'])) { $img_ext = pathinfo($data['CFG_IMAGE_WATER_FILE'], PATHINFO_EXTENSION); $img_ext = strtolower($img_ext); if (!in_array($img_ext, array('jpg', 'gif', 'png', 'jpeg'))) { $this->error('水印图片文件不是图片格式!请重新上传!'); return; }
}
foreach ($data as $k => $v) { $ret = M('config')->where(array('name' => $k))->save(array('s_value' => $v)); } if ($ret !== false) { F('config/site', null); $this->success('修改成功', U('System/site'));
} else {
$this->error('修改失败!'); }
exit(); }
|
添加水印图片——> App/Manage/Controller/PublicController.class.php/67行 upload函数
public function upload($img_flag = 0) { header("Content-Type:text/html; charset=utf-8"); $result = array('state' => '失败', 'url' => '', 'name' => '', 'original' => ''); $sub_path = I('post.sfile', '', 'trim,htmlspecialchars');
$img_flag = empty($img_flag) ? 0 : 1;
$yun_upload = new \Common\Lib\YunUpload($img_flag, $sub_path); $upload_result = $yun_upload->upload();
if ($upload_result['status']) { $result['state'] = 'SUCCESS'; $result['info'] = $upload_result['data']; } else { $result['state'] = $upload_result['info']; } echo json_encode($result);
}
|
App/Common/lib/YunUpload.class.php/ 62行 upload函数
public function upload() { $result = array('status' => 0, 'info' => '', 'data' => array()); if (empty($_FILES)) { $result['info'] = '必须选择上传文件'; return $result; } else {
$info = $this->_upload();
if (isset($info) && is_array($info)) { if (!$this->_saveDate($info)) { $result['status'] = 0; $result['info'] = '上传入库失败'; return $result; }
$new_info = array(); foreach ($info as $k => $v) {
$v['url'] = get_url_path(C('CFG_UPLOAD_ROOTPATH')) . $v['savepath'] . $v['savename']; if ($this->thumFlag) { $imgtbSize = C('CFG_IMGTHUMB_SIZE'); $imgTSize = explode('X', $imgtbSize[0]); if (!empty($imgTSize)) { $v['turl'] = get_picture($v['url'], $imgTSize[0], $imgTSize[1]); } } $new_info[] = $v; }
$result['status'] = 1; $result['info'] = '上传成功'; $result['data'] = $new_info;
return $result;
} else { $result['info'] = '失败:' . $info; return $result; } }
}
|
App/Common/lib/YunUpload.class.php/ 231行 _upload函数 对比上传后缀名是否在网站配置中
public function _upload() { $ext = ''; foreach ($_FILES as $key => $v) { $strtemp = explode('.', $v['name']); $ext = end($strtemp); break; }
$upload = new \Think\Upload(); $upload->autoSub = true; $upload->subType = 'date'; $upload->subName = array('date', 'Ymd'); $upload->maxSize = get_upload_maxsize(); $upload->exts = $this->allowType; $upload->rootPath = $this->rootPath; $upload->savePath = $this->subDirectory; $upload->saveName = array('uniqid', ''); $upload->replace = true; $upload->callback = false;
if ($info = $upload->upload()) {
if ($this->thumFlag) { $this->_doWater($info); }
if ($this->thumFlag) { $this->_doThum($info); } return $info;
} else {
return $upload->getError(); }
}
|
当程序执行到75行时,程序new了 yunupload类,并调用了上传功能。
跳转到YunUpload类中的upload函数,判断是否获取到图片信息,然后调用_upload函数对比图片信息是否与配置文件中一致
遍历获取到上传文件的文件名以及后缀名,程序会将后缀名和allowType数组进行对比,发现后缀名是允许类型,程序会正常执行
当YunUpload类中的upload函数执行完成后,返回上传成功后的文件地址。
程序返回Public类的upload函数执行最后流程,结束后,添加水印图片整个流程结束,webshell上传成功。
回到网站设置App/controller/SystemController.class.php site函数
当执行到遍历代码时,程序会将网站配置所有项全部遍历查找是否存在<?php字符串
当执行到215行时,此处做了一个水印图片后缀过滤,网站获取到的是php后缀,所以此处对比不成功,网站报错提示
但是由于200行处正则处未正确匹配字符串,导致可上传文件,webshell已上传成功,所以此处无法保存不影响漏洞点。
0x02 后台代码执行
漏洞利用
Payload: <? phpinfo(); ?> <?=phpinfo();?>
|
漏洞分析
网站设置——>App/controller/SystemController.class.php/194行 site函数
public function site() { if (IS_POST) {
$data = I('config', array(), 'trim'); foreach ($data as $key => $val) { if (stripos($val, '<?php') !== false) { $data[$key] = preg_replace('/<\?php(.+?)\?>/i', '', $val); } }
$data['CFG_IMGTHUMB_SIZE'] = strtoupper($data['CFG_IMGTHUMB_SIZE']); $data['CFG_IMGTHUMB_SIZE'] = str_replace(array(',', 'X'), array(',', 'X'), $data['CFG_IMGTHUMB_SIZE']); if (empty($data['CFG_IMGTHUMB_SIZE'])) { $this->error('缩略图组尺寸不能为空'); }
if (!empty($data['CFG_IMAGE_WATER_FILE'])) { $img_ext = pathinfo($data['CFG_IMAGE_WATER_FILE'], PATHINFO_EXTENSION); $img_ext = strtolower($img_ext); if (!in_array($img_ext, array('jpg', 'gif', 'png', 'jpeg'))) { $this->error('水印图片文件不是图片格式!请重新上传!'); return; }
}
foreach ($data as $k => $v) { $ret = M('config')->where(array('name' => $k))->save(array('s_value' => $v)); } if ($ret !== false) { F('config/site', null); $this->success('修改成功', U('System/site'));
} else {
$this->error('修改失败!'); }
exit(); }
|
遍历匹配是否存在绕过
遍历完成后,执行到209行后,跳到222行执行,此处会将所有的参数及参数值遍历后存入数据库并且写入config/site文件中保存
而site文件后缀是php,导致代码执行。
0x03 后台任意文件删除
漏洞利用
漏洞点在删除数据库文件备份,抓包
由于未做目录限制,导致可跨目录删除任意文件。可删除install.lock文件来重置网站程序。
漏洞分析
数据库备份->App/manage/controller/databasecontroller.class.php/ 293行 delSqlFiles函数
public function delSqlFiles() {
$id = I('id', 0, 'intval'); $batchFlag = I('get.batchFlag', 0, 'intval'); if ($batchFlag) { $files = I('key', array()); } else { $files[] = I('sqlfilename', ''); }
if (empty($files)) { $this->error('请选择要删除的sql文件'); }
foreach ($files as $file) { unlink($this->getDbPath() . '/' . $file); } $this->success("已删除:" . implode(",", $files), U('Database/restore'));
}
|
最后成功删除license.txt