You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

12 KiB

1. 第三方过滤函数与类

目前大多数应用都有一个参数过滤的统一入口类似于dedecms的代码。

/include/common.inc.php

foreach(Array('_GET','_POST','_COOKIE') as $_request)		// line 52
{
	foreach($$_request as $_k => $_v) ${$_k} = _RunMagicQuotes($_v);
}

_RunMagicQuotes 函数

function _RunMagicQuotes(&$svar)
{
	if(!get_magic_quotes_gpc())
	{
		if( is_array($svar) )
		{
			foreach($svar as $_k => $_v) $svar[$_k] = _RunMagicQuotes($_v);
		}
		else
		{
			$svar = addslashes($svar);
		}
	}
	return $svar;
}

这里的确使用了 addslashes函数对特定的字符进行转义,但是还不够完善。

1.1. discuz SQL安全过滤类分析

discuz专门有一个SQL注入过滤类来过滤SQL注入请求/config/config global.php中。

// -------------------------  CONFIG SECURITY  -------------------------- //  line 57
$_config['security']['authkey'] = '2e4617iIZ7jIVt61';
$_config['security']['urlxssdefend'] = 1;
$_config['security']['attackevasive'] = '0';
$_config['security']['querysafe']['status'] = 1;
$_config['security']['querysafe']['dfunction']['0'] = 'load_file';
$_config['security']['querysafe']['dfunction']['1'] = 'hex';
$_config['security']['querysafe']['dfunction']['2'] = 'substring';
$_config['security']['querysafe']['dfunction']['3'] = 'if';
$_config['security']['querysafe']['dfunction']['4'] = 'ord';
$_config['security']['querysafe']['dfunction']['5'] = 'char';
$_config['security']['querysafe']['daction']['0'] = '@';
$_config['security']['querysafe']['daction']['1'] = 'intooutfile';
$_config['security']['querysafe']['daction']['2'] = 'intodumpfile';
$_config['security']['querysafe']['daction']['3'] = 'unionselect';
$_config['security']['querysafe']['daction']['4'] = '(select';
$_config['security']['querysafe']['daction']['5'] = 'unionall';
$_config['security']['querysafe']['daction']['6'] = 'uniondistinct';
$_config['security']['querysafe']['dnote']['0'] = '/*';
$_config['security']['querysafe']['dnote']['1'] = '*/';
$_config['security']['querysafe']['dnote']['2'] = '#';
$_config['security']['querysafe']['dnote']['3'] = '--';
$_config['security']['querysafe']['dnote']['4'] = '"';
$_config['security']['querysafe']['dlikehex'] = 1;
$_config['security']['querysafe']['afullnote'] = '0';
  • $_config['security']['querysafe']['status'] = 1;设置是否开启SQL注入防御这个选项默认开启
  • 'daction']['dnote']都是SQL注入过滤类的过滤规则规则里包含了常见的注入关键字
  • Discuz 执行 SQL 语句之前会调用 \source\class\discuz\discuz_database.php 文件 discuz_database_safecheck类
  • 下面的checkquery($sql)函数进行过滤
	public static function checkquery($sql) {			// line 347
		if (self::$config === null) {
			self::$config = getglobal('config/security/querysafe');
		}
		if (self::$config['status']) {
			$check = 1;
			$cmd = strtoupper(substr(trim($sql), 0, 3));
			if(isset(self::$checkcmd[$cmd])) {
				$check = self::_do_query_safe($sql);
			} elseif(substr($cmd, 0, 2) === '/*') {
				$check = -1;
			}

			if ($check < 1) {
				throw new DbException('It is not safe to do this query', 0, $sql);
			}
		}
		return true;
	}

从代码中可以看到程序首先加载配置文件中的config/security/querysafe根据 $config['status']判断 SQL 注入防御是否开启,再到 $check = self::_do_query_safe($sql); 可以看到该函数又调用了同类下的_do_query_safe()函数对SQL语句进行过滤。

	private static function _do_query_safe($sql) {
		$sql = str_replace(array('\\\\', '\\\'', '\\"', '\'\''), '', $sql);
		$mark = $clean = '';
		if (strpos($sql, '/') === false && strpos($sql, '#') === false && strpos($sql, '-- ') === false && strpos($sql, '@') === false && strpos($sql, '`') === false) {
			$clean = preg_replace("/'(.+?)'/s", '', $sql);
		} else {
			$len = strlen($sql);
			$mark = $clean = '';
			for ($i = 0; $i < $len; $i++) {
				$str = $sql[$i];
				switch ($str) {
					case '`':
						if(!$mark) {
							$mark = '`';
							$clean .= $str;
						} elseif ($mark == '`') {
							$mark = '';
						}
						break;
					case '\'':
						if (!$mark) {
							$mark = '\'';
							$clean .= $str;
						} elseif ($mark == '\'') {
							$mark = '';
						}
						break;
					case '/':
						if (empty($mark) && $sql[$i + 1] == '*') {
							$mark = '/*';
							$clean .= $mark;
							$i++;
						} elseif ($mark == '/*' && $sql[$i - 1] == '*') {
							$mark = '';
							$clean .= '*';
						}
						break;
					case '#':
						if (empty($mark)) {
							$mark = $str;
							$clean .= $str;
						}
						break;
					case "\n":
						if ($mark == '#' || $mark == '--') {
							$mark = '';
						}
						break;
					case '-':
						if (empty($mark) && substr($sql, $i, 3) == '-- ') {
							$mark = '-- ';
							$clean .= $mark;
						}
						break;

					default:

						break;
				}
				$clean .= $mark ? '' : $str;
			}
		}

		if(strpos($clean, '@') !== false) {
			return '-3';
		}

		$clean = preg_replace("/[^a-z0-9_\-\(\)#\*\/\"]+/is", "", strtolower($clean));

		if (self::$config['afullnote']) {
			$clean = str_replace('/**/', '', $clean);
		}

		if (is_array(self::$config['dfunction'])) {
			foreach (self::$config['dfunction'] as $fun) {
				if (strpos($clean, $fun . '(') !== false)
					return '-1';
			}
		}

		if (is_array(self::$config['daction'])) {
			foreach (self::$config['daction'] as $action) {
				if (strpos($clean, $action) !== false)
					return '-3';
			}
		}

		if (self::$config['dlikehex'] && strpos($clean, 'like0x')) {
			return '-2';
		}

		if (is_array(self::$config['dnote'])) {
			foreach (self::$config['dnote'] as $note) {
				if (strpos($clean, $note) !== false)
					return '-4';
			}
		}

		return 1;
	}

该函数首先使用$sql = str_replace(array('\\\\', '\\\'', '\\"', '\'\''), '', $sql);将SQL语句中的\、'、"以及'、"替换为空紧接着是一个if else判断逻辑来选择过滤的方式

$clean = preg_replace("/'(.+?)'/s", '', $sql);

  • 这段代码表示当SQL语句里存在'/'、'#'、'-- '、'@'、'`'这些字符时,则直接调用 preg_replace()函数将单引号(')中间的内容替换为空
  • 正则表达式中?匹配前面的子表达式零次或一次。
  • /s默认情况下的圆点 . 是匹配除换行符 \n 之外的任何字符,加上 s 修饰符之后,圆点 . 中包含换行符 \n。

else条件中是对整段SQL语句进行逐个字符进行判断:

else {
			$len = strlen($sql);
			$mark = $clean = '';
			for ($i = 0; $i < $len; $i++) {
					case '/':
						if (empty($mark) && $sql[$i + 1] == '*') {
							$mark = '/*';
							$clean .= $mark;
							$i++;
						} elseif ($mark == '/*' && $sql[$i - 1] == '*') {
							$mark = '';
							$clean .= '*';
						}
						break;

这段代码的逻辑是当检查到SQL语句中存在斜杠(/)时,则去判断下一个字符是不是星号(),如果是星号()就把这两个字符拼接起来,即/,然后继续判断下一个字符是不是星号(),如果是星号则再继续拼接起来,得到/**

最后在如下代码中判断是否存在原来拦截规则里面定义的字符如果存在则拦截SQL语句执行

		if (is_array(self::$config['dnote'])) {
			foreach (self::$config['dnote'] as $note) {
				if (strpos($clean, $note) !== false)
					return '-4';
			}
		}

1.2. discuz XSS标签过滤函数分析

在XSS的防御上只要过滤掉尖括号以及单、 双引号就能干掉绝大部分的payload。

下面是discuz的HTML标签过滤代码在/source/function/function_blog.php文件中

function checkhtml($html) {			// line 271
	if(!checkperm('allowhtml')) {

		preg_match_all("/\<([^\<]+)\>/is", $html, $ms);

		$searchs[] = '<';
		$replaces[] = '&lt;';
		$searchs[] = '>';
		$replaces[] = '&gt;';

		if($ms[1]) {
			$allowtags = 'img|a|font|div|table|tbody|caption|tr|td|th|br|p|b|strong|i|u|em|span|ol|ul|li|blockquote';
			$ms[1] = array_unique($ms[1]);
			foreach ($ms[1] as $value) {
				$searchs[] = "&lt;".$value."&gt;";

				$value = str_replace('&amp;', '_uch_tmp_str_', $value);
				$value = dhtmlspecialchars($value);
				$value = str_replace('_uch_tmp_str_', '&amp;', $value);

				$value = str_replace(array('\\','/*'), array('.','/.'), $value);
				$skipkeys = 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','javascript','script','eval','behaviour','expression','style','class');
				$skipstr = implode('|', $skipkeys);
				$value = preg_replace(array("/($skipstr)/i"), '.', $value);
				if(!preg_match("/^[\/|\s]?($allowtags)(\s+|$)/is", $value)) {
					$value = '';
				}
				$replaces[] = empty($value)?'':"<".str_replace('&quot;', '"', $value).">";
			}
		}
		$html = str_replace($searchs, $replaces, $html);
	}

	return $html;
}

preg_match_all("/\<([^\<]+)\>/is", $html, $ms);正则取岀来尖括号中间的内容然后在if($ms[l])这个if条件里对这些标签中的关键字进行筛选。

$skipkeys变量定义了很多on事件的敏感字符。

最后拼接正则将这些字符串替换掉。

				$skipstr = implode('|', $skipkeys);
				$value = preg_replace(array("/($skipstr)/i"), '.', $value);

2. PHP内置过滤函数

2.1. SQL注入过滤函数

  • SQL 注入过滤函数有 addslashes()、mysql_real_escape_string()以及 mysql_escape_string(),
  • 它们的作用都是给字符串添加反斜杠()来转义掉单引号(')、双引号('')、反斜线()以及空字符 NULL。

2.2. XSS注入过滤函数

htmlspecialchars()函数的作用是将字符串中的特殊字符转换成HTML实体编码

  • & 转换成&amp;

  • " 转换成&quot;

  • ' 转换成&#039;

  • < 转换成&lt;

  • > 转换成&gt;

strip_tags()函数则是用来去掉HTML及PHP标记比如给这个函数传入 <h1>xxxxx</h1>经过它处理后返回的字符串为 xxxxx

2.3. 命令执行过滤函数

PHP为了防止系统命令注入的漏洞提供了 escapeshellcmd()和escapeshellarg()两个函数对参数进行过滤。