刚开始代码审计多多少少会有种无从下手的感觉,想要通过自己的代码审计能力直接审计出漏洞未免有些困难。但是如果直接拿到一个cms开始审,刚开始可能富有激情,可是越审到后面就会越怀疑自己,然后放弃代码审计。造成这样的现象的原因便是:不知道自己到底能不能审到漏洞。
那如何审出一个很少人知道,并且肯定存在的洞呢?
本文介绍的办法就是:上cnvd看看以前师傅提交的洞,虽然大多数的洞都是不公开的,但是我们还是可以根据cnvd给的一点点信息来尝试复现。
因此,本文涉及到的漏洞都是比较早被发现,危害性较低,并且已经被厂商修复的漏洞,本文仅对代码审计的过程做一个分析。
刚开始审计,可以选择一些比较冷门的cms(内容管理系统),挑选一个自己擅长的cms语言审计,之所以选择冷门的cms是因为这些cms一般存在的安全问题会比热门cms多。
本文选用的cms是:emlog(V5.3.1),这个cms是使用PHP语言开发的个人博客管理系统
确定完目标之后,就需要上cnvd搜索该cms历史存在的一些漏洞,本文演示的是emlog
可以看到在cnvd上存在这么一些漏洞,虽然在漏洞详情页面并没有直接写漏洞是怎么形成的,但是在这里或多或少会存在一些信息。比如在哪个地方会存在什么类型的洞。
接下里挑选一个简单的漏洞进行演示
由上图可知,在后台页面,一般是admin目录下,有一个ta打头的文件,而我们查看一下emlog目录,唯一一个ta打头的文件就是tags.php
而在tag.php仅有50余行代码,因此想在50行左右的代码地方找到一个SQL注入还是比较简单的
首先是大概了解一下这50余行代码的作用,对于一个博客内容管理系统来说,tag.php应该是一个处理文章标签管理的文件
因此我们这个cms可能会用到这个文件的页面
在这个页面中存在几种操作,一个是全选,一个是删除,其中点进标签后还存在标签修改的功能
这几个功能对应在tag.php中就是这么几个if语句
而执行哪一种操作是根据传入action的值来决定的
因此,接来下审计在这些if语句中可能存在的SQL注入的点
首先是第一个if判断
if ($action == '') {
$tags = $Tag_Model->getTag();
include View::getView('header');
require_once View::getView('tag');
include View::getView('footer');
View::output();
}
这一个if判断只有一个action是可控的,其余没有参数可控,因此这个函数不存在SQL注入的点
然后再看一下第二个if判断
if ($action== "mod_tag") {
$tagId = isset($_GET['tid']) ? intval($_GET['tid']) : '';
$tag = $Tag_Model->getOneTag($tagId);
extract($tag);
include View::getView('header');
require_once View::getView('tagedit');
include View::getView('footer');View::output();
}
这一个if判断相比上一个多一个tid为可控参数,不过cms对传入的tid进行了intval的转换
不过我们还是跟进getOneTag方法看一下
function getOneTag($tagId) {
$tag = array();
$row = $this->db->once_fetch_array("SELECT tagname,tid FROM ".DB_PREFIX."tag WHERE tid=$tagId");
$tag['tagname'] = htmlspecialchars(trim($row['tagname']));
$tag['tagid'] = intval($row['tid']);
return $tag;
}
getOneTag方法具有一个参数tagId,这个tagId就是前面我们自定义传入的tid,不过这个地方没法传入字符串进行闭合注入,因为在传入这个方法以前就已经intval转换了,所以这个点不存在sql注入
接下来看第三个if判断
//标签修改
if ($action=='update_tag') {
$tagName = isset($_POST['tagname']) ? addslashes($_POST['tagname']) : '';
$tagId = isset($_POST['tid']) ? intval($_POST['tid']) : '';
if (empty($tagName)) {
emDirect("tag.php?action=mod_tag&tid=$tagId&error_a=1");
}
$Tag_Model->updateTagName($tagId, $tagName);
$CACHE->updateCache(array('tags', 'logtags'));
emDirect("./tag.php?active_edit=1");
}
这里相比上面多了一个tagname,这个tagname可以传入字符串,也就是有可能会存在SQL注入,不过在传入的时候,cms会对这个tagname进行一个addslashes函数转换,也就是传入的单引号以及一些特殊符号会被转译。
我们这边抓包尝试一下
首先查看一下现在的标签,此时有两个标签tag112以及tags2123
上面提到的tagname其实就是标签的名字,我们抓包将tag112修改为tag112',尝试能否闭合
这时候我们返回主页面
发现此时标签变为了tag112',所以单引号是被转义了,没法直接进行SQL注入。
当然,如果数据库的编码为GBK,那么可以尝试宽字节注入,不过默认使用的数据库为utf8,因此无法使用宽字节注入。
接下来看第4个if判断
//批量删除标签
if ($action== 'dell_all_tag') {
$tags = isset($_POST['tag']) ? $_POST['tag'] : '';
LoginAuth::checkToken();
if (!$tags) {
emDirect("./tag.php?error_a=1");
}
foreach ($tags as $key=>$value) {
$Tag_Model->deleteTag($key);
}
$CACHE->updateCache(array('tags', 'logtags'));
emDirect("./tag.php?active_del=1");
}
这个地方POST传入了一个tag,是我们的可控参数,并且这里没有对tag进行转义,所以我们再往下看是否存在SQL注入
其中我们传入的tag最终会成为tags数组中的key,仔细看一下foreach循环中,传入的deleteTag方法的参数是数组中的key,而在PHP中,数组的key并不一定需要是数字,也可以是一个字符串,只要key和value对应即可。
我们这个时候先不着急抓包,先看看deleteTag方法是如何实现的。
跟进deleteTag方法
function deleteTag($tagId) {
$this->db->query("DELETE FROM ".DB_PREFIX."tag where tid=$tagId");
}
可以发现,SQL语句执行的时候也是没有进行任何过滤的。
这里我们开始抓包
可以看到post传入了tag[1],这里我们对方括号中的1开始注入,这里也不需要引号闭合,直接注即可。
我使用的是报错注入
tag%5B1%20and%20updatexml(0,concat(0x7e,version()),1)%20%23%5D=1&token=c16eaff79a17d690f5c0caae66276085
看下方的结果,已经把数据库的版本爆出来了。
本文所复现的是在后台的SQL注入,利用难度较大,危害性较低。并且审计这个cms并不是直接拿一个cms从头开始审,而是根据在cnvd中存在的一些模糊信息进行审计,来提高自己复现这个漏洞的成功率,不至于在一开始就碰壁,而能在相对短的时间里获得较大的成就感,提高学习效率。