一、概述
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'));