进入六月以后,自己的时间也就充裕很多了。很多比赛结束、绿盟那边暂时也没有什么任务,就等着7月份去入职了。所以沉下心准备学点东西。于是就选择一位老哥审计过的源码,自己来审计一遍,看看自己的差距,也给自己增加点经验。再就是上次土司聚会就说会在土司发表一篇文章……所以潜水很多年的我来了。本文章是审计的一个CMS系统,比较乱,希望别介意。
我的审计思路一般是:->看目录摸清大体的框架->找具体功能审计->选择漏洞类型进行审计。
图1 主要目录Tree
看目录很明显,有后台目录、缓存目录、数据储存目录、安装目录、插件目录以及手机版的目录。大体的目录了解了后,就开始正式的审计了。 首先看下入口文件index.php,发现很有趣,按照常规的入口文件,一般是引入核心文件什么的,而他直接就是将一些过滤代码写到了入口文件。
// 预防XSS漏洞
foreach ($_GET as $k => $v) {
$_GET[$k] = htmlspecialchars($v);
}
$dbm = new db_mysql();
//预处理搜索时的值,主要是防止sql的注入
if (isset($_GET['q'])) {
if (isset($_GET['act']) && $_GET['act'] == 'hot') {
if (trim($_GET['q']) == '') {
$sql = "SELECT id,q,qnum FROM " . TB_PREFIX . "search_keyword LIMIT 15";
$res = $dbm->query($sql);
if (empty($res['error']) && is_array($res['list'])) {
foreach ($res['list'] as $k => $v) {
$res['list'][$k]['q'] = helper :: utf8_substr($v['q'], 0, 20);
}
echo json_encode($res['list']);
exit;
} else {
die();
}
}
}
//超出长度截取
if (strlen($_GET['q']) > 20) {
$_GET['q'] = helper :: utf8_substr($_GET['q'], 0, 20);
}
if (trim($_GET['q']) == '0' || trim($_GET['q']) == '') die('搜索词不能为0或空,请重新输入。点此 <a href ="' . SITE_PATH . '">回到首页</a>');
if (!preg_match("/^[{4e00}-{9fa5}{0}]+$/u", $_GET['q'])) {
die('搜索词只允许下划线,数字,字母,汉字和空格,请重新输入。点此<a href ="' . SITE_PATH . '">回到首页</a>');
}
可以看出,系统做了两个过滤。一个是htmlspecialchars(v),另一个是/^[\x{4e00}-\x{9fa5}\w {0}]+/u。前一个过滤是把预定义的字符 "<" 和 ">"转换为 HTML 实体,后一个是用正则处理参数,使其只能输入下划线、数字、字母、汉字和空格。
继续看,下面的代码主要是加载分类板块,本以为没有什么希望在这个文件寻找出什么漏洞的时候,最后几段代码让我眼前一亮。
if (substr($tpl, strlen($tpl) - 4, 4) == '.php') {
$tmp_file = '/templates/' . $from_mobile . '/' . $tpl;
} else {
$tmp_file = '/templates/' . $from_mobile . '/' . $tpl . '.php';
}
if (!file_exists(dirname(__FILE__) . $tmp_file)) die('模板页面不存在' . $tmp_file);
require(dirname(__FILE__) . $tmp_file);
首先构建php文件,然后判断该php文件是否存在,如果不存在,直接die,如果存在,引入该文件。没有任何过滤或者判断,很明显的文件包含漏洞!!于是测试查看phpinfo的信息。在根目录下创建了一个phpinfo文件。
图2 phpinfo文件
然后根据代码,来构建playload。
图3 包含结果
成功读取。 到这里有两个利用的思路。一是读取相关敏感信息,二是利用该漏洞上传文件带有一句话的文件,通过该包含漏洞进行链接菜刀。第一个尝试了一下,没有发现什么有效的敏感信息。后台什么的,都是js文件和php文件操作,这个包含也看不了什么信息,于是就把目光转向了第二个。既然是上传,肯定要找上传点,发现前台并没有什么上传点,于是尝试着后台,发现一个神奇的地方。
图4 上传点
app/upload/upload_form.php?params=%7B%22inner_box%22%3A%22%23ff1%22%2C%22func%22%3A%22callback_upload_resource%22%2C%22id%22%3A%221%22%2C%22thumb%22%3A%7B%22width%22%3A%22300%22%2C%22height%22%3A%22300%22%7D%2C%22domain%22%3A%22localhost%22%7D
这个上传点竟然没有上传权限限制,就是说只要知道这个地址,可以传任何图片、APK文件到服务器上。然后查看了下upload_form.php文件。果然如此,只是一个上传文件的格式判断以及传入参数判断,如果参数正确,就可以上传,没有验证访问者的权限。
$upload_server= SITE_PATH."upload/";
//上传安全验证字符串
$verify=helper::encrypt(UPLOAD_CODE.strtotime(date('Y-m-d H:i:s')),UPLOAD_KEY);
$params=$_GET['params'];
$params=preg_replace('~(\)~','"',$params);
$json=json_decode($params);
这就有意思了。 于是结合上面的那个文件包含漏洞,上传一个含有一句话的图片,尝试获取shell。
图5 一句话木马图片上传
PS:这里有个问题,就是图片上传后,文件名是随机的,实际操作的时候,可能要扫目录或者其他方法获取文件名。 因为在服务器上储存的是jpg文件,如果直接访问的话,肯定是显示不存在模板,如下:
图6 尝试读取
随即自然的想起了%00截断。由于本地环境的php版本是5.2.17<5.3.4,而且并没有开启magic_quotes_gpc所以是可以截断成功的。如下:
图7 %00截断
http://localhost/app/index.php?tpl=../../upload/img/2017/06/11/ 593cc2106fd93.jpg%00&id=1
菜刀成功连接之。
图8 菜刀连接
看完了入口文件,开始审计其他内容,首先从安装开始,很遗憾,并没有发现什么漏洞,想从注册和登陆这块审计,无奈这套系统又不存在这个功能,所以就开始了针对于漏洞的审计。发现根目录下的pic.php存在问题。代码如下:
if(isset($_GET['url']) && trim($_GET['url']) != '' && isset($_GET['type'])) {
$img_url=trim($_GET['url']);
$img_url = base64_decode($img_url);
$img_url=strtolower(trim($img_url));
$_GET['type']=strtolower(trim($_GET['type']));
$urls=explode('.',$img_url);
if(count($urls)<=1) die('image type forbidden 0');
$file_type=$urls[count($urls)-1];
if(in_array($file_type,array('jpg','gif','png','jpeg'))){}else{ die('image type foridden 1');}
if(strstr($img_url,'php')) die('image type forbidden 2');
if(strstr($img_url,chr(0)))die('image type forbidden 3');
if(strlen($img_url)>256)die('url too length forbidden 4');
header("Content-Type: image/{$_GET['type']}");
readfile($img_url);
} else {
die('image not find£¡');
}
以GET的请求方式,传入两个参数:url和type,要求url参数必须是base64格式,然后系统经过转变成小写后进行判断验证。判断有很多,主要是判断url传入参数的长度、内容,如果长度大于256和小于等于1,包含关键字php、非标准化路径统统报错,并且type类型只能是jpg/gif/png/jpeg。
程序编写者明显是有安全意识的。但是这里还是存在两个漏洞的,一个是文件包含漏洞,一个是文件下载漏洞。
上面的确是对url参数的做了几次验证,但是开发者忽略了编码转换。就是说,如果我们讲%00、php等类似的字符进行url编码转换以后,再进行base64加密,传入参数后,这几个验证是可以绕过的!
我们根据上面的文件包含漏洞来构建playload。
其中dXBsb2FkL2ltZy8yMDE3LzA2LzEwLzU5M2NjMjEwNmZkOTMlMmUlNzAlNjglNzAlMjUlMzAlMzAuanBn解密后是upload/img/2017/06/10/593cc2106fd93%2e%70%68%70%25%30%30.jpg 这里的%2e%70%68%70%25%30%30是.php%00。然后菜刀连接之。
图9 连接地址
图10 成功连接
还有一个漏洞就是header("Content-Type: image/{$_GET['type']}");
这里我们只要构造type的类型不等于in_array()中的任何一个条件,Content-Type:image/tpye 就会因为头文件错误导致无法正确解析源文件,造成直接下载源文件的现象。(大家可尝试在php的文件里加上header("Content-Type: image/php");看看效果
继续看文件,发现了很多XSS漏洞,不过都是反射型的,这里就找两个说吧。
第一处:/templates/m/inc_head.php
<input type="text" id="abc" class="search-txt" value="<?php if(isset($_GET['q'])) echo $_GET['q'];?>" />
很明显的xss漏洞,直接判断参数q是否存在,然后输出。
直接构建playload:
http://localhost/app/templates/m/search.php?q="/><script>alert('xss')</script>
成功触发漏洞。
图11 XSS漏洞(1)
第二处:/templates/m/search.php
<title>ËÑË÷ <?php if(isset($_GET['q'])) echo $_GET['q'];?> - <?php echo SITE_NAME;?></title>
同上,直接构造playload:
http://localhost/app/templates/m/search.php?q=a<;/title><script>alert('xss')</script>
成功触发漏洞。
图12 XSS漏洞(2)
最尴尬的是,inc_head.php 这个文件是存在xss漏洞的,但是很多文件都引入了这个文件,也就导致了很多地方存在了此漏洞。
对于文件包含漏洞,可以设置类似白名单的方法,通过筛选固定文件名方法。这样一方面不必切断这个业务,另一方面又不会被轻易绕过,当然,也可以采用设置open_basedir的方法来防御。
对于XSS漏洞,没什么好说的,本套系统之所以出现XSS漏洞,是因为没有类似于PC端那种直接使用正则进行参数验证,所以只要使用Index.php的那种过滤,是可以的。
这次审计对于自己算是一次进步和总结吧,最遗憾的是没有审计到sql注入漏洞,因为入口的那个正则过滤以及始终无法闭合双引号(双引号被转义成实体),所以始终没有注入成功。
有兴趣的可以下载这套系统审计看看,就当练手。最后,有不足的地方欢迎指出,我会听取各位大佬的意见。轻喷哈~