美文网首页网络安全学习
骑士cms-通读全文-代码审计

骑士cms-通读全文-代码审计

作者: x00c | 来源:发表于2020-04-03 21:28 被阅读0次

    版本号:3.5.1

    下载地址:http://103.45.101.75:66/2/201412/74cms.rar

    1.审计方法

    通读审计

    1.1查看文件结构

    首先需要看看有哪些文件和文件夹,寻找名称里有没有带有api、admin、manage、include一类关键字的文件和文件夹,通常这些文件比较重要,在这个程序里,可以看到并没有什么PHP文件,就一个index.php,看到有一个名为include的文件夹,一般比较核心的文件都会放在这个文件夹中,我们先来看看大概有哪些文件

    image.png

    1.2 查看关键文件
    在include里面,common.fun.php就是本程序的核心,大多数功能都在这里实现。

    我们来看一下里面都有哪些关键函数

    一开始就看到SQL注入过滤函数

    function addslashes_deep($value)
    {
        if (empty($value))
        {
            return $value;
        }
        else
        {
        if (!get_magic_quotes_gpc())
        {
        $value=is_array($value) ? array_map('addslashes_deep', $value) : mystrip_tags(addslashes($value));
        }
        else
        {
        $value=is_array($value) ? array_map('addslashes_deep', $value) : mystrip_tags($value);
        }
        return $value;
        }
    }
    

    该函数将传人的变量使用addslashes()函数进行过滤,也就过滤掉了单引号、双引号、NULL字符以及斜杠,现在我们要记住,在挖掘SQL注入等漏洞时,只要参数在拼接到SQL语句前,除非有宽字节注入或者其他特殊情况,否则使用了这个函数就不能注入了。

    再往下走是一个XSS过滤的函数mystrip_tags

    function mystrip_tags($string)
    {
       $string = new_html_special_chars($string);
       $string = remove_xss($string);
       return $string;
    }
    

    下面调用了new_html_special_chars和remove_xss函数去处理。

    在new_html_special_chars()函数中可以看到,这个函数对&符号、双引号以及尖括号进行了html实体编码,并且使用striptags()函数进行了二次过滤。而remove_xss()函数则是对一些标签关键字、事件关键字以及敏感函数关键字进行了替换。

    function new_html_special_chars($string) {
    
       $string = str_replace(array('&amp;', '&quot;', '<', '>'), array('&', '"', '<', '>'), $string);
    
       $string = strip_tags($string);
    
       return $string;
    
    }
    
    function remove_xss($string) {
    
       $string = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S', '', $string);
    
    
       $parm1 = Array('javascript', 'union','vbscript', 'expression', 'applet', 'xml', 'blink', 'link', 'script', 'embed', 'object', 'iframe', 'frame', 'frameset', 'ilayer', 'layer', 'bgsound', 'title', 'base');
    
    
       $parm2 = Array('onabort', 'onactivate', 'onafterprint', 'onafterupdate', 'onbeforeactivate', 'onbeforecopy', 'onbeforecut', 'onbeforedeactivate', 'onbeforeeditfocus', 'onbeforepaste', 'onbeforeprint', 'onbeforeunload', 'onbeforeupdate', 'onblur', 'onbounce', 'oncellchange', 'onchange', 'onclick', 'oncontextmenu', 'oncontrolselect', 'oncopy', 'oncut', 'ondataavailable', 'ondatasetchanged', 'ondatasetcomplete', 'ondblclick', 'ondeactivate', 'ondrag', 'ondragend', 'ondragenter', 'ondragleave', 'ondragover', 'ondragstart', 'ondrop', 'onerror', 'onerrorupdate', 'onfilterchange', 'onfinish', 'onfocus', 'onfocusin', 'onfocusout', 'onhelp', 'onkeydown', 'onkeypress', 'onkeyup', 'onlayoutcomplete', 'onload', 'onlosecapture', 'onmousedown', 'onmouseenter', 'onmouseleave', 'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup', 'onmousewheel', 'onmove', 'onmoveend', 'onmovestart', 'onpaste', 'onpropertychange', 'onreadystatechange', 'onreset', 'onresize', 'onresizeend', 'onresizestart', 'onrowenter', 'onrowexit', 'onrowsdelete', 'onrowsinserted', 'onscroll', 'onselect', 'onselectionchange', 'onselectstart', 'onstart', 'onstop', 'onsubmit', 'onunload','style','href','action','location','background','src','poster');
    
       
    
       $parm3 = Array('alert','sleep','load_file','confirm','prompt','benchmark','select','update','insert','delete','alter','drop','truncate','script','eval');
    
    
       $parm = array_merge($parm1, $parm2, $parm3);
    
    
       for ($i = 0; $i < sizeof($parm); $i++) {
    
           $pattern = '/';
    
           for ($j = 0; $j < strlen($parm[$i]); $j++) {
    
               if ($j > 0) {
    
                   $pattern .= '(';
    
                   $pattern .= '(&#[x|X]0([9][a][b]);?)?';
    
                   $pattern .= '|($#0([9][10][13]);?)?';
    
                   $pattern .= ')?';
    
               }
    
               $pattern .= $parm[$i][$j];
    
           }
    
           $pattern .= '/i';
    
           $string = preg_replace($pattern, '****', $string);
    
       }
    
       return $string;
    
    }
    

    再往下就有获取IP的函数,此处可以伪造IP。其它程序在获取IP时没有验证IP格式,也可能利用获取IP进行注入。

    
    function getip()
    {
      if (getenv('HTTP_CLIENT_IP') and strcasecmp(getenv('HTTP_CLIENT_IP'),'unknown')) {
        $onlineip=getenv('HTTP_CLIENT_IP');
      }elseif (getenv('HTTP_X_FORWARDED_FOR') and strcasecmp(getenv('HTTP_X_FORWARDED_FOR'),'unknown')) {
        $onlineip=getenv('HTTP_X_FORWARDED_FOR');
      }elseif (getenv('REMOTE_ADDR') and strcasecmp(getenv('REMOTE_ADDR'),'unknown')) {
        $onlineip=getenv('REMOTE_ADDR');
      }elseif (isset($_SERVER['REMOTE_ADDR']) and $_SERVER['REMOTE_ADDR'] and strcasecmp($_SERVER['REMOTE_ADDR'],'unknown')) {
        $onlineip=$_SERVER['REMOTE_ADDR'];
      }
      preg_match("/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/",$onlineip,$match);
      return $onlineip = $match[0] ? $match[0] : 'unknown';
    }
    

    下面是inserttable和updatetable函数,这里有大量的SQL语句进行查询,主要看有没有过滤问题。

    function inserttable($tablename, $insertsqlarr, $returnid=0, $replace = false, $silent=0)
    
    function updatetable($tablename, $setsqlarr, $wheresqlarr, $silent=0)
    

    再往下走则是wheresql()函数,是SQL语句查询的Where条件拼接的地方,我们可以看到参数都使用了单引号进行包裹。

    function wheresql($wherearr='')
    
    {
    
       $wheresql="";
    
       if (is_array($wherearr))
    
           {
    
           $where_set=' WHERE ';
    
               foreach ($wherearr as $key => $value)
    
               {
    
               $wheresql .=$where_set. $comma.$key.'="'.$value.'"';
    
               $comma = ' AND ';
    
               $where_set=' ';
    
               }
    
           }
    
       return $wheresql;
    
    }
    

    还有一个访问令牌生成的函数asyn_userkey(),拼接用户名、密码salt以及密码进行一次md5,访问的时候只要在GET参数key的值里面加上生成的这个key即可验证是否有权限,被用在注册、找回密码等验证过程中,也就是我们能看到的找回密码链接里面的key。

    function asyn_userkey($uid)
    
    {
    
       global $db;
    
       $sql = "select * from ".table('members')." where uid = '".intval($uid)."' LIMIT 1";
    
       $user=$db->getone($sql);
    
       return md5($user['username'].$user['pwd_hash'].$user['password']);
    
    }
    

    同目录下是具体功能的文件,可以先不看


    image.png

    1.3 查看配置文件

    查找目录下的config文件


    image.png

    发现/data下的cache_config和config才是配置文件。

    <?php
    
    $dbhost   = "localhost";
    
    $dbname   = "74cms";
    
    $dbuser   = "root";
    
    $dbpass   = "root";
    
    $pre    = "qs_";
    
    $QS_cookiedomain = '';
    
    $QS_cookiepath =  "/74cms/";
    
    $QS_pwdhash = "H@g24Q6xa:AewjJD";
    
    define('QISHI_CHARSET','gb2312');
    
    define('QISHI_DBCHARSET','GBK');
    
    ?>
    

    可以看到是,QISHI_DBCHARSET常量是GBK编码的,因此上面和数据相关的双引号解析代码处,可能存在宽字节注入。不过需要看数据库连接时设置的编码

    接着找数据库连接文件/include/mysql.class.php中的connect函数

    
    function connect($dbhost, $dbuser, $dbpw, $dbname = '', $dbcharset = 'gbk', $connect=1){
      $func = empty($connect) ? 'mysql_pconnect' : 'mysql_connect';
      if(!$this->linkid = @$func($dbhost, $dbuser, $dbpw, true)){
        $this->dbshow('Can not connect to Mysql!');
      } else {
        if($this->dbversion() > '4.1'){
          mysql_query( "SET NAMES gbk");
          if($this->dbversion() > '5.0.1'){
            mysql_query("SET sql_mode = ''",$this->linkid);
      mysql_query("SET character_set_connection=".$dbcharset.", character_set_results=".$dbcharset.", character_set_client=binary", $this->linkid);
          }
        }
      }
      if($dbname){
        if(mysql_select_db($dbname, $this->linkid)===false){
          $this->dbshow("Can't select MySQL database($dbname)!");
        }
      }
    }
    

    也就是当MySQL版本大于4.1时执行“set names gbk”,但当小于5.0.1时,下面不执行。

    只执行set names gbk。

    但set names gbk 等价于
    设置客户端的编码

    set character_set_client=gbk
    设置连接器编码
    set character_set_connection=gbk
    设置返回值编码
    set character_set_results=gbk
    client(客户端)、connection(连接器)、results(返回值)

    所以,在MySQL4.1-5.0.1之间都存在宽字节注入。

    1.4 阅读首页文件

    通过对系统文件大概的了解,我们对这套程序的整体架构已经有了一定的了解,但是还不够,所以我们得跟读一下index.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');
    

    跟进到common.inc.php查看

    require_once(QISHI_ROOT_PATH.'data/config.php');
    
    header("Content-Type:text/html;charset=".QISHI_CHARSET);
    
    require_once(QISHI_ROOT_PATH.'include/common.fun.php');
    
    require_once(QISHI_ROOT_PATH.'include/74cms_version.php');
    

    可以看到引用了config.php为配置文件,common.fun.php是核心功能文件,74cms_version.php是版本文件

    继续往下,可以看到对传输的数据进行过滤

    if (!empty($_GET))
    
    {
    
    $_GET  = addslashes_deep($_GET);
    
    }
    
    if (!empty($_POST))
    
    {
    
    $_POST = addslashes_deep($_POST);
    
    }
    
    $_COOKIE   = addslashes_deep($_COOKIE);
    
    $_REQUEST  = addslashes_deep($_REQUEST);
    

    再往下看到一个包含文件的操作

    require_once(QISHI_ROOT_PATH.'include/tpl.inc.php');
    

    跟进到tpl.inc.php文件

    include_once(QISHI_ROOT_PATH.'include/template_lite/class.template.php');
    
    $smarty = new Template_Lite;
    
    $smarty -> cache_dir = QISHI_ROOT_PATH.'temp/caches/'.$_CFG['template_dir'];
    
    $smarty -> compile_dir =  QISHI_ROOT_PATH.'temp/templates_c/'.$_CFG['template_dir'];
    
    $smarty -> template_dir = QISHI_ROOT_PATH.'templates/'.$_CFG['template_dir'];
    
    $smarty -> reserved_template_varname = "smarty";
    
    $smarty -> left_delimiter = "{#";
    
    $smarty -> right_delimiter = "#}";
    
    $smarty -> force_compile = false;
    
    $smarty -> assign('_PLUG', $_PLUG);
    
    $smarty -> assign('QISHI', $_CFG);
    
    $smarty -> assign('page_select',$page_select);
    

    可以看到进行了一个模版文件映射,并对smarty进行赋予属性。

    现在回到index.php文件

    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);
    
    }
    

    判断是否存在缓存,然后用display将页面展示。

    到这里发现了特定数据库版本存在宽字节注入,并对骑士CMS的代码有了更多了解。

    image.png

    相关文章

      网友评论

        本文标题:骑士cms-通读全文-代码审计

        本文链接:https://www.haomeiwen.com/subject/rxllphtx.html