代码审计(全文通读审计案例)

骑士 cms 通读审计案例

我们已经介绍了代码审计中通读全文代码审计方式的思路,下面我们用案例来说明
这种通读方式。为了方便大家理解,笔者找了一款相对简单容易看懂的应用骑士cms来介绍,版本是3.5.1,具体的审计思路我们在上文中已经有过介绍。

1. 查看应用文件结构

先看一下骑士 cms 的大致文件目录结构,如下图:
在这里插入图片描述
首先需要看看有哪些文件和文件夹,寻找名称里有没有带有apiadminmanageinclude 一类关键字的文件和文件夹,通常这些文件比较重要,在这个程序里,可以看到并没有什么PHP文件,就一个index.php,看到有一个名为include的文件夹,一般比较核心的文件都会放在这个文件夹中,我们先来看看大概有哪些文件,如下图所示。
在这里插入图片描述

2. 查看关键文件代码

在这个文件夹里面我们看到了多个数十K的PHP文件,比如common.fun.php就是本程序的核心文件(函数集文件),基础函数基本在这个文件中实现,我们来看看这个文件里有哪些关键函数,一打开这个文件,立马就看到一大堆过滤函数这是我们最应该关心的地方,首先是一个SQL注入过滤函数:

function addslashes_deep($value)
{
    if (empty($value))
    {
        return $value;
    }
    else
    {
		if (!get_magic_quotes_gpc())  // get_magic_quotes_gpc获取当前magic_quotes_gpc的配置选项设置
		{
		$value=is_array($value) ? array_map('addslashes_deep', $value) : mystrip_tags(addslashes($value)); // strip_tags() 函数剥去字符串中的 HTML、XML 以及 PHP 的标签。
// array_map() 函数将用户自定义函数作用到数组中的每个值上,并返回用户自定义函数作用后的带有新值的数组。
		}
		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() 函数来过滤XSS,就在该函数下方,代码如下:

function new_html_special_chars($string) {
	$string = str_replace(array('&amp;', '&quot;', '&lt;', '&gt;'), 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); //array_merge函数把一个或多个数组合并为一个数组

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

在new_html_special_chars() 函数中可以看到,这个函数对&符号、双引号以及尖括号进行了html实体编码,并且使用strip_tags() 函数进行了二次过滤。而remove_xss()函数则是对一些标签关键字、事件关键字以及敏感函数关键字进行了替换。再往下走有一个获取IP地址的函数getip()是可以伪造IP地址的
在这里插入图片描述
getenv(参数)函数是一个用于获取环境变量的函数,根据提供不同的参数可以获取不同的环境变量。strcasecmp() 函数比较两个字符串,相等返回0,大于返回正数,小于返回负数。

很多应用都会由于在获取IP时没有验证IP格式,而存在注入漏洞,不过这里还只是可以伪造IP(用抓包)。

再往下看可以看到一个值得关注的地方,SQL查询统一操作函数inserttable()以及updatetable()函数,大多数SQL语句执行都会经过这里,所以我们要关注这个地方是否还有过滤等问题

function inserttable($tablename, $insertsqlarr, $returnid=0, $replace = false, $silent=0) {
	global $db;    // 声明 $db 的作用范围为全局,度就可以在函数里面使用了
	$insertkeysql = $insertvaluesql = $comma = '';
	foreach ($insertsqlarr as $insert_key => $insert_value) {
		$insertkeysql .= $comma.'`'.$insert_key.'`';
		$insertvaluesql .= $comma.'\''.$insert_value.'\'';
		$comma = ', ';
	}
	$method = $replace?'REPLACE':'INSERT';
	// echo $method." INTO $tablename ($insertkeysql) VALUES ($insertvaluesql)", $silent?'SILENT':'';die;
	$state = $db->query($method." INTO $tablename ($insertkeysql) VALUES ($insertvaluesql)", $silent?'SILENT':'');
	if($returnid && !$replace) {
		return $db->insert_id();
	}else {
	    return $state;
	} 
}

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

再往下走则是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,代码如下:
在这里插入图片描述
同目录下的文件如下图所示:
在这里插入图片描述
图中是具体功能的实现代码,我们这时候还不需要看,先了解下程序的其他结构。

3. 查看配置文件

接下来我们找找配置文件,之前我们介绍到配置文件的文件名通常都带有 “config” 这样的关键字,我们只要搜索带有这个关键字的文件名即可,如下图所示。
在这里插入图片描述
在搜索结果中我们可以看到搜索出来多个文件,结合文件所在目录这个经验可以判断出data目录下面的config.php以及 cache_config.php 才是真正的配置文件,打开/data/config.php查看代码,如下所示: .

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

很明显看到,很有可能存在我们之前说过的双引号解析代码执行的问题,通常这个配置是在安装系统的时候设置的,或者后台也有设置的地方。另外我们还应该记住的一个点是QISHI_DBCHARSET常量,这里配置的数据库编码是GBK,也就可能存在宽字节注入,不过需要看数据库连接时等设置的编码,不妨找找看,找到骑士cms连接MySQL的代码在include\mysql.class.php文件的connect()函数,代码如下:
在这里插入图片描述
这段代码里面有个关键的地方,见选中部分的代码,这里存在安全隐患。
代码首先判断MySQL版本是否大于4.1,如果是则执行如下代码:

mysql_query( "SET NAMES gbk");

执行这个语句之后再判断,如果大于 5 则执行如下代码:

mysql_query("SET character_set_connection=".$dbcharset.", character_set_results=".$dbcharset.", character_set_client=binary", $this->linkid);

character_set_client 客户端使用的编码,如GBK, UTF8 比如你写的sql语句是什么编码的。
character_set_results 查询返回的结果集的编码(从数据库读取的数据是什么编码的)。
character_set_connection 连接使用的编码

也就是说在MySQL版本小于 5 的情况下是不会执行这行代码的,但是执行了“set names gbk",“set names gbk"其实等同于干了三件事:

SET character_set_connection=' gbk ', character_set_results=' gbk ', character_set_client=' gbk '

因此在MySQL版本大于 4.1 小于 5 的情况下,基本所有跟数据库有关的操作都存在宽字节注入。

跟读首页Index文件

通过对系统文件大概的了解,我们对这套程序的整体架构已经有了一定的了解,但是还不够,所以我们得跟读一下index.php文件,看看程序运行的时候会调用哪些文件和函数

打开首页文件index.php就可以看到如下代码:
在这里插入图片描述
首先判断安装锁文件是否存在,如果不存在则跳转到install/index.php,接下来是包含/include/common.inc.php文件,跟进该文件查看:
在这里插入图片描述
发现/include/common.inc.php文件在开头包含了三个文件,data/config.php 为数据库配置文件,include/common.fun.php 文件为基础函数库集文件,include/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);

这段代码调用了 include/common.fun.php 文件里面的 addslashes_deep() 函数对GET/POST/COOKIE参数进行了过滤,再往下走可以看到又有一个包含文件的操作:

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

包含了 include/tpl.inc.php 文件,跟进看看这个文件做了什么:
在这里插入图片描述
首先看到包含了 include/template_lite/classtemplate.php 文件,这是一个映射程序模板的,由Paul Lockaby paul 和Mark Dickenson编写,由于该文件较大,我们这里不再仔细分析,继续往下跟进,可以看到这段代码实例化了这个类对象赋值给$smarty变量,继续跟进则回转到index.php文件代码:
在这里插入图片描述
判断是否已经缓存,然后调用display()函数输出页面,审计到这里是否对整个程序的框架比较熟悉了?接下来像审计index.php文件一样跟进其他功能入口文件即可完成代码通读。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值