74cms代码审计–准备工作和配置函数的审计

74cms代码审计

前期准备工作

本地搭建好环境:

图片[1]-74cms代码审计–准备工作和配置函数的审计-Drton1博客

工具打开源码:

图片[2]-74cms代码审计–准备工作和配置函数的审计-Drton1博客

这是我第一个系统性审计的一个整套代码,采用先通读全文性的方法进行审计,然后根据敏感关键字回溯参数传递过程进行审计 该程序大概源码有数万行,希望能够学到不少东西,提升自己的代码审计能力,同时也希望能发现不少漏洞,我不会把每个函数代码都写出来 那要写到明年去了我会把核心公用的代码进行分析,然后重点就去寻找存在漏洞的地方了。

通读全文代码也有一定的技巧,并不是随便找文件逐个读完就可以了,这样你是很难真正读懂这套Web程序的,也很难理解代码的业务逻辑,首先我们要看程序的大体代码结构,如主目录有哪些文件,模块目录有哪些文件,插件目录有哪些文件,除了关注有哪些文件,还要注意文件的大小、创建时间。我们根据这些文件的命名就可以大致知道这个程序实现了哪些功能,核心文件是哪些,discuz的程序主目录如图所示。

图片[3]-74cms代码审计–准备工作和配置函数的审计-Drton1博客

在看程序目录结构的时候,我们要特别注意以下几个文件:

1)函数集文件,通常命名中包含functions或者common等关键字,这些文件里面是一些公共的函数,提供给其他文件统一调用,所以大多数文件都会在文件头部包含到其他文件。寻找这些文件一个非常好用的技巧就是去打开index.php或者一些功能性文件,在头部一般都能找到。

2)配置文件通常命名中包括config关键字配置文件包括Web程序运行必须的功能性配置选项以及数据库等配置信息。

3)安全过滤文件 ,安全过滤文件对我们做代码审计至关重要,关系到我们挖掘到的可疑点能不能利用,通常命名中有filter、safe、check等关键字 ,这类文件主要是对参数进行过滤,比较常见的是针对SQL注入和XSS过滤,还有文件路径、执行的系统命令的参数,其他的则相对少见。而目前大多数应用都会在程序的入口循环对所有参数使用addslashes()函数进行过滤。

4)index文件 ,index是一个程序的入口文件,所以通常我们只要读一遍index文件就可以大致了解整个程序的架构、运行的流程、包含到的文件,其中核心的文件又有哪些。而不同目录的index文件也有不同的实现方式,建议最好先将几个核心目录的index文件都简单读一遍。

全局函数的审计:common.fun.php

看目录图: 有一个include 的文件夹一般比较核心的文件都会放在这个文件夹中,我们先来看看大概有哪些文件 也就是我们常说的基本全局都会用的配置内容。

接下来让我们静下心,细细品味程序世界的每一处风景。

这个文件在include目录当中看名字就知道是公用的功能函数配置,我们先拿它来开刀

addslashes_deep()函数:

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

这时这个php文件的第一个函数, 可以看到它是php的对sql语句的过滤因为addslashes()函数本身就是对一些符号比如单引号 斜杠等的转义, 进行分析,看看这个代码怎么写的。

先接收一个value 如果是空的那就直接返回了

如果不是空的那就进行下一步 看看魔术符号开了没,如果开了 那么传入的值会被PHP去完成转义,那他就不用再次进行过滤了。

        $value=is_array($value) ? array_map('addslashes_deep', $value) : mystrip_tags(addslashes($value));

可以看到这里他进行了一次判断 判断是不是数组,如果是数组就调用**array_map()**这个函数

array_map() 函数将用户自定义函数作用到数组中的每个值上,并返回用户自定义函数作用后的带有新值的数组。

这是啥意思呢 意思就是如果value是数组 就把value每个值都放进这个**addslashes_deep()**过一遍。检测一遍,那如果value是数组把他元素再当作value再进来一次这个函数 ,有点递归的味道。

那么再次传进来的元素就不是数组了 就先执行**addslashes()函数,这个函数就是对sql注入那些需要的单引号各种符号就行转义 加一个斜杠,这个是php自带的函数。然后转义后的结果执行mystrip_tags()**这个函数,这个函数不是php自带的函数而是自己写的,我们全局搜索找到了这个自己定义的函数。

mystrip_tags()函数:

图片[4]-74cms代码审计–准备工作和配置函数的审计-Drton1博客

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

就是对传进来的字符串,再传入两个函数一次,这两个函数也是自己写的 第一次把字符串传入了new_html_special_chars()函数,这个函数就在mystrip_tags()的下面。

new_html_special_chars()函数:

function new_html_special_chars($string) {
   $string = str_replace(array('&', '"', '<', '>'), array('&', '"', '<', '>'), $string);
   $string = strip_tags($string);
   return $string;
}

可以看到他是把这个字符串如果中的html字符进行实例化转义一次,就是防止xss攻击。

再看下一个

函数remove_xss()函数:

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

   $parm3 = Array('alert','sleep','load_file','confirm','prompt','benchmark','select','update','insert','delete','create','alter','drop','truncate');

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

仔细看了看代码 就是定义了三个数组 然后把js关键字都写入,最后两个for循环让pattern形成一个正则匹配式的格式,这三个数组所有的关键字都在里面,再与string进行比较 把出现关键字的地方用星号代替, 而这里为什么要写成这个for循环的形式 我觉得是为了更好的维护,比如那天多了一个关键字什么的他只用加进去数组就好了,又一个开发小知识get。

再次回到第一函数:

如果开启了魔术符号 那就不执行一下addslashes()函数进行过滤, 因为php会自己过滤。直接把value交给mystrip_tags()函数。

那么到这里就已经知道这个函数的基本作用了 就是把传进来的value值就行sql,xss的过滤,防止发生这两个的攻击。

我们再接着往下走,来到下一个函数

table()函数:

function table($table)
{
   global $pre;
    return $pre .$table ;
}

这个函数不长哈哈哈,但是还真的不怎么能看出来他到底干啥用的,可能是让数据形成表格用来显示的。再继续往下看:

showmsg()函数

function showmsg($msg_detail, $msg_type = 0, $links = array(), $auto_redirect = true,$seconds=3)
{
   global $smarty;
    if (count($links) == 0)
    {
        $links[0]['text'] = '返回上一页';
        $links[0]['href'] = 'javascript:history.go(-1)';
    }
   $smarty->assign('ur_here',     '系统提示');
   $smarty->assign('msg_detail',  $msg_detail);
   $smarty->assign('msg_type',    $msg_type);
   $smarty->assign('links',       $links);
   $smarty->assign('default_url', $links[0]['href']);
   $smarty->assign('auto_redirect', $auto_redirect);
   $smarty->assign('seconds', $seconds);
   $smarty->display('showmsg.htm');
exit;
}

看这个函数的名多半就知道干嘛用的了吧哈哈哈,显示信息用的,这里我猜都能猜出来大概那个场景能用的,就是显示信息的时候如果这时最后一页什么下面没了,这个时候links 就是链接后面内容用的 ,他的个数就是0 然后就会提示返回下一页。

(13条消息) PHP Smarty assign(分配)变量_houyanhua1的博客-CSDN博客

通过这个博客可以知道assign就是php模板用来实现分配变量用的 就是预先写好一个html模板

然后上面预留一些可以变化的值,让后端接到数据可以实时的显示 修改这个页面的特定地方的值。

没啥好说的继续看下一个:

get_smarty_request()函数:

function get_smarty_request($str)
{
$str=rawurldecode($str);
$strtrim=rtrim($str,']');
   if (substr($strtrim,0,4)=='GET[')
   {
   $getkey=substr($strtrim,4);
   return $_GET[$getkey];
   }
   elseif (substr($strtrim,0,5)=='POST[')
   {
   $getkey=substr($strtrim,5);
   return $_POST[$getkey];
   }
   else
   {
   return $str;
   }
}

smarty是这个程序用的模板名字,这个函数看名字就知道是 接受这个模板的请求调用的方法。

先进行一次url的解码 然后把这个字符串中’]’ 以及右边的字符移除。

然后几个判断 判断到底是那种请求方式 然后再返回对应的接受方式。

再看下一个函数:

get_cache()函数:

function get_cache($cachename)
{
   $cache_file_path =QISHI_ROOT_PATH. "data/cache_".$cachename.".php";
   @include($cache_file_path);
   return $data;
}

看一看到这是一个查询指定名字的缓存,然后返回。 也没啥研究的 ,我们通读代码每个函数都看一遍等后面看功能点时候指定他调用哪里就好。

exectime()函数:

function exectime(){
   $time = explode(" ", microtime());
   $usec = (double)$time[0];
   $sec = (double)$time[1];
   return $sec + $usec;
}

看函数名字是计算时间。 microtime()是返回当前 Unix 时间戳的微秒数:

explode() 函数使用一个字符串分割另一个字符串,并返回由字符串组成的数组。

表示以空格

图片[5]-74cms代码审计–准备工作和配置函数的审计-Drton1博客

就是把当前这个结果给组合成1666962592.2088800秒 然后返回

继续看下一个函数

check_word()函数:

function check_word($noword,$content)
{
   $word=explode('|',$noword);
   if (!empty($word) && !empty($content))
   {
      foreach($word as $str)
      {
         if(!empty($str) && strstr($content,$str))
         {
         return true;
         }

      }
   }
   return false;
}

这个意思就是如果noword中有content的值就返回真。 知道他的作用 再看代码就很容易了。

再看下一个函数:

getip()函数:

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

这个函数名一看知道干啥的 获取ip。

看内容getenv(’HTTP_CLIENT_IP’)含义就是获得当前用户的ip

整个函数的目的就是拿到你的真实ip 同时他也会验证拿到IP的格式是否符合

如果拿不到就返回 unknown

再看下一个函数:

convertip()函数:

function convertip($ip) {
   if(preg_match("/^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$/", $ip)) {
      $iparray = explode('.', $ip);
      if($iparray[0] == 10 || $iparray[0] == 127 || ($iparray[0] == 192 && $iparray[1] == 168) || ($iparray[0] == 172 && ($iparray[1] >= 16 && $iparray[1] <= 31))) {
         $return = '- LAN';
      } elseif($iparray[0] > 255 || $iparray[1] > 255 || $iparray[2] > 255 || $iparray[3] > 255) {
         $return = '- Invalid IP Address';
      } else {
         $tinyipfile =QISHI_ROOT_PATH.'data/tinyipdata.dat';
         if(@file_exists($tinyipfile)) {
            $return = convertip_tiny($ip, $tinyipfile);
         }
      }
   }
   return $return;
}

这个函数就是将IP地址转换为真实地址

先判断传进来的ip 不是内网ip 不是不存在的ip

然后调用 data/tinyipdata.dat的文件 返回真实的ip。

convertip_tiny()函数:

function convertip_tiny($ip, $ipdatafile) {
   static $fp = NULL, $offset = array(), $index = NULL;
   $ipdot = explode('.', $ip);
   $ip    = pack('N', ip2long($ip));
   $ipdot[0] = (int)$ipdot[0];
   $ipdot[1] = (int)$ipdot[1];
   if($fp === NULL && $fp = @fopen($ipdatafile, 'rb')) {
      $offset = @unpack('Nlen', @fread($fp, 4));
      $index  = @fread($fp, $offset['len'] - 4);
   } elseif($fp == FALSE) {
      return  '- Invalid IP data file';
   }
   $length = $offset['len'] - 1028;
   $start  = @unpack('Vlen', $index[$ipdot[0] * 4] . $index[$ipdot[0] * 4 + 1] . $index[$ipdot[0] * 4 + 2] . $index[$ipdot[0] * 4 + 3]);

   for ($start = $start['len'] * 8 + 1024; $start < $length; $start += 8) {

      if ($index{$start} . $index{$start + 1} . $index{$start + 2} . $index{$start + 3} >= $ip) {
         $index_offset = @unpack('Vlen', $index{$start + 4} . $index{$start + 5} . $index{$start + 6} . "\\x0");
         $index_length = @unpack('Clen', $index{$start + 7});
         break;
      }
   }
   @fseek($fp, $offset['len'] + $index_offset['len'] - 1024);
   if($index_length['len']) {
      return '- '.@fread($fp, $index_length['len']);
   } else {
      return '- Unknown';
   }
}

跟上面那个函数搭配的 上面的那个函数调用了这个函数 ,我们不必看懂 只用知道这两个函数 是把你ip转化为地址的作用即可。

inserttable()函数:

function inserttable($tablename, $insertsqlarr, $returnid=0, $replace = false, $silent=0)
 {
   global $db;
   $insertkeysql = $insertvaluesql = $comma = '';
   foreach ($insertsqlarr as $insert_key => $insert_value) {
      $insertkeysql .= $comma.'`'.$insert_key.'`';
      $insertvaluesql .= $comma.'\\''.$insert_value.'\\'';
      $comma = ', ';
   }
   $method = $replace?'REPLACE':'INSERT';
   $state = $db->query($method." INTO $tablename ($insertkeysql) VALUES ($insertvaluesql)", $silent?'SILENT':'');
   if($returnid && !$replace) {
      return $db->insert_id();
   }else {
       return $state;
   }
}

可以看到这个函数是对 sql语句的insert语句进行封装了,他能插入 数据或者id 也就是说这个cms 看到这里 就要注意 他既然这样写操作函数,说明这个cms中任何调用sql语句insert的地方都会用到这个函数。 我们知道就好 至于安全性 我门后面针对功能点进行验证。

updatetable()函数:

function updatetable($tablename, $setsqlarr, $wheresqlarr, $silent=0) {
   global $db;
   $setsql = $comma = '';
   foreach ($setsqlarr as $set_key => $set_value) {
      if(is_array($set_value)) {
         $setsql .= $comma.'`'.$set_key.'`'.'='.$set_value[0];
      } else {
         $setsql .= $comma.'`'.$set_key.'`'.'=\\''.$set_value.'\\'';
      }
      $comma = ', ';
   }
   $where = $comma = '';
   if(empty($wheresqlarr)) {
      $where = '1';
   } elseif(is_array($wheresqlarr)) {
      foreach ($wheresqlarr as $key => $value) {
         $where .= $comma.'`'.$key.'`'.'=\\''.$value.'\\'';
         $comma = ' AND ';
      }
   } else {
      $where = $wheresqlarr;
   }
   return $db->query("UPDATE ".($tablename)." SET ".$setsql." WHERE ".$where, $silent?"SILENT":"");
}

这个同理就是把sql语句的update给封装起来,我们现在只需要知道 当其他模块要更新数据库中的东西时候 会调用这个函数。知道这里就可以,至于他的运行原理,后面有程序用到时候 拼接一下就知道他的逻辑了。

wheresql()函数:

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

简而言之就是把sql语句的where 条件限制都给封装了, 看到这里这个作者= = 怎么把所有的sql操作全封装了,更安全吗?

convert_datefm ()函数:

function convert_datefm ($date,$format,$separator="-")
{
    if ($format=="1")
    {
    return date("Y-m-d", $date);
    }
    else
    {
      if (!preg_match("/^[0-9]{4}(\\\\".$separator.")[0-9]{1,2}(\\\\1)[0-9]{1,2}(|\\s+[0-9]{1,2}(|:[0-9]{1,2}(|:[0-9]{1,2})))$/",$date))  return false;
      $date=explode($separator,$date);
      return mktime(0,0,0,$date[1],$date[2],$date[0]);
    }
}

date() 函数用于格式化时间/日期。

就是对传进来的数据转化为时间/日期的那种格式,如果不是纯数字的数据 就直接返回假

sub_day()函数:

function sub_day($endday,$staday,$range='')
{
   $value = $endday - $staday;
   if($value < 0)
   {
      return '';
   }
   elseif($value >= 0 && $value < 59)
   {
      return ($value+1)."秒";
   }
   elseif($value >= 60 && $value < 3600)
   {
      $min = intval($value / 60);
      return $min."分钟";
   }
   elseif($value >=3600 && $value < 86400)
   {
      $h = intval($value / 3600);
      return $h."小时";
   }
   elseif($value >= 86400 && $value < 86400*30)
   {
      $d = intval($value / 86400);
      return intval($d)."天";
   }
   elseif($value >= 86400*30 && $value < 86400*30*12)
   {
      $mon  = intval($value / (86400*30));
      return $mon."月";
   }
   else{
      $y = intval($value / (86400*30*12));
      return $y."年";
   }
}

这个就是计算 两个日期的差值 然后进行返回。

daterange()函数:

这个功能跟上面的差不多但是就是范围没有上面的大,这个应该是类似于记录那种文章啊什么 多少时间前发布用的

function daterange($endday,$staday,$format='Y-m-d',$color='',$range=3)
{
   $value = $endday - $staday;
   if($value < 0)
   {
      return '';
   }
   elseif($value >= 0 && $value < 59)
   {
      $return=($value+1)."秒前";
   }
   elseif($value >= 60 && $value < 3600)
   {
      $min = intval($value / 60);
      $return=$min."分钟前";
   }
   elseif($value >=3600 && $value < 86400)
   {
      $h = intval($value / 3600);
      $return=$h."小时前";
   }
   elseif($value >= 86400)
   {
      $d = intval($value / 86400);
      if ($d>$range)
      {
      return date($format,$staday);
      }
      else
      {
      $return=$d."天前";
      }
   }
   if ($color)
   {
   $return="<span style=\\"color:{$color}\\">".$return."</span>";
   }
   return $return;
}

cut_str()函数:

function cut_str($string, $length, $start=0,$dot='')
{
      $length=$length*2;
      if(strlen($string) <= $length) {
         return $string;
      }
      $string = str_replace(array('&', '"', '<', '>'), array('&', '"', '<', '>'), $string);
      $strcut = '';
         for($i = 0; $i < $length; $i++) {
            $strcut .= ord($string[$i]) > 127 ? $string[$i].$string[++$i] : $string[$i];
         }
      $strcut = str_replace(array('&', '"', '<', '>'), array('&', '"', '<', '>'), $strcut);
      return $strcut.$dot;
}

这个函数讲道理一开始真没看明白他想干什么。仔细分析一波 才看明白

为什么ascii码大于127就直接添加两个? 因为大于127的就是汉字了 而汉字是由两个ascii码表示的

说到这里应该明白这个函数干嘛的了吧,他是用来显示指定长度的字符串,超出长度以自定义dot的符号填补尾部

smtp_mail()函数:

function smtp_mail($sendto_email,$subject,$body,$From='',$FromName='')
{
   global $_CFG;
   require_once(QISHI_ROOT_PATH.'phpmailer/class.phpmailer.php');
   $mail = new PHPMailer();
   $mailconfig=get_cache('mailconfig');
   $mailconfig['smtpservers']=explode('|-_-|',$mailconfig['smtpservers']);
   $mailconfig['smtpusername']=explode('|-_-|',$mailconfig['smtpusername']);
   $mailconfig['smtppassword']=explode('|-_-|',$mailconfig['smtppassword']);
   $mailconfig['smtpfrom']=explode('|-_-|',$mailconfig['smtpfrom']);
   $mailconfig['smtpport']=explode('|-_-|',$mailconfig['smtpport']);
   for ($i=0; $i<count($mailconfig['smtpservers']); $i++)
   {
   $mailconfigarray[]=array('smtpservers'=>$mailconfig['smtpservers'][$i],'smtpusername'=>$mailconfig['smtpusername'][$i],'smtppassword'=>$mailconfig['smtppassword'][$i],'smtpfrom'=>$mailconfig['smtpfrom'][$i],'smtpport'=>$mailconfig['smtpport'][$i]);
   }
   $mc=array_rand($mailconfigarray,1);
   $mc=$mailconfigarray[$mc];
   $mailconfig['smtpservers']=$mc['smtpservers'];
   $mailconfig['smtpusername']=$mc['smtpusername'];
   $mailconfig['smtppassword']=$mc['smtppassword'];
   $mailconfig['smtpfrom']=$mc['smtpfrom'];
   $mailconfig['smtpport']=$mc['smtpport'];
   $From=$From?$From:$mailconfig['smtpfrom'];
   $FromName=$FromName?$FromName:$_CFG['site_name'];
   if ($mailconfig['method']=="1")
   {
      if (empty($mailconfig['smtpservers']) || empty($mailconfig['smtpusername']) || empty($mailconfig['smtppassword']) || empty($mailconfig['smtpfrom']))
      {
      write_syslog(2,'MAIL',"邮件配置信息不完整");
      return false;
      }
   $mail->IsSMTP();
   $mail->Host = $mailconfig['smtpservers'];
   $mail->SMTPDebug= 0;
   $mail->SMTPAuth = true;
   $mail->Username = $mailconfig['smtpusername'];
   $mail->Password = $mailconfig['smtppassword'];
   $mail->Port =$mailconfig['smtpport'];
   $mail->From =$mailconfig['smtpfrom'];
   $mail->FromName =$FromName;
   }
   elseif($mailconfig['method']=="2")
   {
   $mail->IsSendmail();
   }
   elseif($mailconfig['method']=="3")
   {
   $mail->IsMail();
   }
   $mail->CharSet =QISHI_CHARSET;
   $mail->Encoding = "base64";
   $mail->AddReplyTo($From,$FromName);
   $mail->AddAddress($sendto_email,"");
   $mail->IsHTML(true);
   $mail->Subject = $subject;
   $mail->Body =$body;
   $mail->AltBody ="text/html";
   if($mail->Send())
   {
   return true;
   }
   else
   {
   write_syslog(2,'MAIL',$mail->ErrorInfo);
   return false;
   }
}

这个函数是实现发送邮件用的。 看一个函数先知道他作用 然后再去看代码 就迎刃而解了 。

这块代码是php 用smtp发邮件的标准代码块。

dfopen()函数:

function dfopen($url,$limit = 0, $post = '', $cookie = '', $bysocket = FALSE    , $ip = '', $timeout = 15, $block = TRUE, $encodetype  = 'URLENCOD')
{
      $return = '';
      $matches = parse_url($url);
      $host = $matches['host'];
      $path = $matches['path'] ? $matches['path'].($matches['query'] ? '?'.$matches['query'] : '') : '/';
      $port = !empty($matches['port']) ? $matches['port'] : 80;

      if($post) {
         $out = "POST $path HTTP/1.0\\r\\n";
         $out .= "Accept: */*\\r\\n";
         //$out .= "Referer: $boardurl\\r\\n";
         $out .= "Accept-Language: zh-cn\\r\\n";
         $boundary = $encodetype == 'URLENCODE' ? '' : ';'.substr($post, 0, trim(strpos($post, "\\n")));
         $out .= $encodetype == 'URLENCODE' ? "Content-Type: application/x-www-form-urlencoded\\r\\n" : "Content-Type: multipart/form-data$boundary\\r\\n";
         $out .= "User-Agent: $_SERVER[HTTP_USER_AGENT]\\r\\n";
         $out .= "Host: $host:$port\\r\\n";
         $out .= 'Content-Length: '.strlen($post)."\\r\\n";
         $out .= "Connection: Close\\r\\n";
         $out .= "Cache-Control: no-cache\\r\\n";
         $out .= "Cookie: $cookie\\r\\n\\r\\n";
         $out .= $post;
      } else {
         $out = "GET $path HTTP/1.0\\r\\n";
         $out .= "Accept: */*\\r\\n";
         //$out .= "Referer: $boardurl\\r\\n";
         $out .= "Accept-Language: zh-cn\\r\\n";
         $out .= "User-Agent: $_SERVER[HTTP_USER_AGENT]\\r\\n";
         $out .= "Host: $host:$port\\r\\n";
         $out .= "Connection: Close\\r\\n";
         $out .= "Cookie: $cookie\\r\\n\\r\\n";
      }

      if(function_exists('fsockopen')) {
         $fp = @fsockopen(($ip ? $ip : $host), $port, $errno, $errstr, $timeout);
      } elseif (function_exists('pfsockopen')) {
         $fp = @pfsockopen(($ip ? $ip : $host), $port, $errno, $errstr, $timeout);
      } else {
         $fp = false;
      }
      if(!$fp) {
         return '';
      } else {
         stream_set_blocking($fp, $block);
         stream_set_timeout($fp, $timeout);
         @fwrite($fp, $out);
         $status = stream_get_meta_data($fp);
         if(!$status['timed_out']) {
            while (!feof($fp)) {
               if(($header = @fgets($fp)) && ($header == "\\r\\n" ||  $header == "\\n")) {
                  break;
               }
            }

            $stop = false;
            while(!feof($fp) && !$stop) {
               $data = fread($fp, ($limit == 0 || $limit > 8192 ? 8192 : $limit));
               $return .= $data;
               if($limit) {
                  $limit -= strlen($data);
                  $stop = $limit <= 0;
               }
            }
         }
         @fclose($fp);
         return $return;
      }
}

这个学过网络的看着是不是很熟悉,没错就是socket编程, 把网络数据包发到指定ip

想象一下别人访问你 你不回应别人怎么行呢,这个就是服务器回应别人的方法。(我觉得是 等后面打脸就尬了 )

send_sms()函数:

function send_sms($mobile,$content)
{
   global $db;
   $sms=get_cache('sms_config');
   if ($sms['open']!="1" || empty($sms['sms_name']) || empty($sms['sms_key']) || empty($mobile) || empty($content))
   {
   return false;
   }
   else
   {
   return dfopen("<http://www.74cms.com/SMSsend.php?sms_name={$sms['sms_name']}&sms_key={$sms['sms_key']}&mobile={$mobile}&content={$content}>");
   }
}

这个函数看着怎么像那种你用了他家程序 给他家反馈?他来接受 或者就是激活这个cms高级版本的发送程序?哈哈 反正就是跟开发这个cms的交互。

execution_crons()函数:

function execution_crons()
{
   global $db;
   $crons=$db->getone("select * from ".table('crons')." WHERE (nextrun<".time()." OR nextrun=0) AND available=1 LIMIT 1  ");
   if (!empty($crons))
   {
      require_once(QISHI_ROOT_PATH."include/crons/".$crons['filename']);
   }
}

图片[6]-74cms代码审计–准备工作和配置函数的审计-Drton1博客

看到这里懂了吧 就是他去查一下数据库里有没有要执行的任务 如果有 就去执行它。

get_tpl()函数

function get_tpl($type,$id)
{
   global $db,$_CFG,$smarty;
   $id=intval($id);
   $tarr=array("jobs","company_profile","resume","companycommentshow","companynewsshow",'course','train_profile');
   if (!in_array($type,$tarr)) exit();
   if ($type=='companynewsshow')
   {
   $utpl=$db->getone("SELECT p.tpl FROM ".table('company_news')."  AS c LEFT JOIN ".table('company_profile')."  AS p ON c.company_id=p.id WHERE c.id='{$id}' limit 1");
   }
   else
   {
   $utpl=$db->getone("SELECT tpl FROM ".table($type)." WHERE id='{$id}' limit 1");
   }
   $thistpl=$utpl['tpl'];
   if (!empty($_GET['style']))
   {
   $thistpl=$_GET['style'];
   }
   if (empty($thistpl))
   {
      if ($type=='resume')
      {
      $thistpl="../tpl_resume/{$_CFG['tpl_personal']}/";
      $smarty->assign('user_tpl',$_CFG['site_dir']."templates/tpl_resume/{$_CFG['tpl_personal']}/");
      return $thistpl;
      }
      elseif ($type=='course' || $type=='train_profile' || $type=='trainnewsshow')
      {
      $thistpl="../tpl_train/{$_CFG['tpl_train']}/";
      $smarty->assign('user_tpl',$_CFG['site_dir']."templates/tpl_train/{$_CFG['tpl_train']}/");
      return $thistpl;
      }
      else
      {
      $thistpl="../tpl_company/{$_CFG['tpl_company']}/";
      $smarty->assign('user_tpl',$_CFG['site_dir']."templates/tpl_company/{$_CFG['tpl_company']}/");
      return $thistpl;
      }
   }
   else
   {
      if ($type=='resume')
      {
      $smarty->assign('user_tpl',$_CFG['site_dir']."templates/tpl_resume/{$thistpl}/");
      return "../tpl_resume/{$thistpl}/";
      }
      else
      {
      $smarty->assign('user_tpl',$_CFG['site_dir']."templates/tpl_company/{$thistpl}/");
      return "../tpl_company/{$thistpl}/";
      }
   }
}

不用看懂 这个都是调用模板写好的函数,tpl就是模板,就是调用模板用的。等后面看见哪里调用了知道就行,因为我们不搞开发,我这次之所以都全都看一遍 是想对整个源码有个系统性的认识。

url_rewrite()函数:

function url_rewrite($alias=NULL,$get=NULL,$rewrite=true)
{
   global $_CFG,$_PAGE;
   $url ='';
   if ($_PAGE[$alias]['url']=='0' || $rewrite==false)//原始链接
   {
         if (!empty($get))
         {
            foreach($get as $k=>$v)
            {
            $url .="{$k}={$v}&";
            }
         }
         $url=!empty($url)?"?".rtrim($url,'&'):'';
         return $_CFG['site_domain'].$_CFG['site_dir'].$_PAGE[$alias]['file'].$url;
   }
   else
   {
         $url =$_CFG['site_domain'].$_CFG['site_dir'].$_PAGE[$alias]['rewrite'];
         if ($_PAGE[$alias]['pagetpye']=='2' && empty($get['page']))
         {
         $get['page']=1;
         }
         foreach($get as $k=>$v)
         {
         $url=str_replace('($'.$k.')',$v,$url);
         }

         $url=preg_replace('/\\(\\$(.+?)\\)/','',$url);
         if(substr($url,-5)=='?key=')
         {
         $url=rtrim($url,'?key=');
         }
         return $url;
   }
}

这个应该就是实现网页重定向的功能。

get_member_url()函数:

function get_member_url($type,$dirname=false)
{
   global $_CFG;
   $type=intval($type);
   if ($type===0)
   {
   return "";
   }
   elseif ($type===1)
   {
   $return=$_CFG['site_dir']."user/company/company_index.php";
   }
   elseif ($type===2)
   {
   $return=$_CFG['site_dir']."user/personal/personal_index.php";
   }
   elseif ($type===3)
   {
   $return=$_CFG['site_dir']."user/hunter/hunter_index.php";
   }
   elseif ($type===4)
   {
   $return=$_CFG['site_dir']."user/train/train_index.php";
   }
   if ($dirname)
   {
   return dirname($return).'/';
   }
   else
   {
   return $return;
   }
}

这个看函数中内容都能猜个差不多,这个就是个人中心那些url。

剩下的都是不怎么重要的函数,就是知道跟不知道都行,我们主要功能函数就分析到这,

接下来重点去分析漏洞之间的逻辑。

© 版权声明
THE END
喜欢就支持一下吧
点赞6 分享
评论 抢沙发

请登录后发表评论