之前我们分析了CI框架的session类session.php,本文我们继续分析CI框架的安全类security.php文件,方便我们更详细的了解CI框架,从而更熟练的应用CI框架
CI安全类提供了全局防御CSRF攻击和XSS攻击策略,只需要在配置文件开启即可:
复制代码 代码如下:
$config['csrf_protection'] = TRUE;
$config['global_xss_filtering'] = TRUE;
并提供了实用方法:
复制代码 代码如下:
$this->security->xss_clean($data);//第二个参数为TRUE,验证图片安全
$this->security->sanitize_filename()//过滤文件名
CI也提供了安全函数:
xss_clean()//xss过滤
sanitize_filename()//净化文件名
do_hash()//md5或sha加密
strip_image_tags() //删除图片标签的不必要字符
encode_php_tags()//把PHP脚本标签强制转成实体对象
复制代码 代码如下:
/**
* 安全类
*/
class CI_Security {
//url的随机hash值
protected $_xss_hash = '';
//防csrf攻击的cookie标记的哈希值
protected $_csrf_hash = '';
//防csrf cookie过期时间
protected $_csrf_expire = 7200;
//防csrf的cookie名称
protected $_csrf_token_name = 'ci_csrf_token';
//防csrf的token名称
protected $_csrf_cookie_name = 'ci_csrf_token';
//不允许出现的字符串数组
protected $_never_allowed_str = array(
'document.cookie' => '[removed]',
'document.write' => '[removed]',
'.parentNode' => '[removed]',
'.innerHTML' => '[removed]',
'window.location' => '[removed]',
'-moz-binding' => '[removed]',
'' => '-->',
' '
'
);
//不允许出现的正则表达式数组
protected $_never_allowed_regex = array(
'javascripts*:',
'expressions*((|()', // CSS and IE
'vbscripts*:', // IE, surprise!
'Redirects 302',
"(["'])?datas*:[^\1]*?base64[^\1]*?,[^\1]*?\1?"
);
//构造函数
public function __construct()
{
// CSRF保护是否开启
if (config_item('csrf_protection') === TRUE)
{
// CSRF配置
foreach (array('csrf_expire', 'csrf_token_name', 'csrf_cookie_name') as $key)
{
if (FALSE !== ($val = config_item($key)))
{
$this->{'_'.$key} = $val;
}
}
// _csrf_cookie_name加上cookie前缀
if (config_item('cookie_prefix'))
{
$this->_csrf_cookie_name = config_item('cookie_prefix').$this->_csrf_cookie_name;
}
// 设置csrf的hash值
$this->_csrf_set_hash();
}
log_message('debug', "Security Class Initialized");
}
// --------------------------------------------------------------------
/**
* 验证跨站请求伪造保护
*
* @return 对象
*/
public function csrf_verify()
{
// 如果不是post请求,则设置csrf的cookie值
if (strtoupper($_SERVER['REQUEST_METHOD']) !== 'POST')
{
return $this->csrf_set_cookie();
}
// Do the tokens exist in both the _POST and _COOKIE arrays?
if ( ! isset($_POST[$this->_csrf_token_name], $_COOKIE[$this->_csrf_cookie_name]))
{
$this->csrf_show_error();
}
// token匹配吗
if ($_POST[$this->_csrf_token_name] != $_COOKIE[$this->_csrf_cookie_name])
{
$this->csrf_show_error();
}
// We kill this since we're done and we don't want to
// polute the _POST array
unset($_POST[$this->_csrf_token_name]);
// Nothing should last forever
unset($_COOKIE[$this->_csrf_cookie_name]);
$this->_csrf_set_hash();
$this->csrf_set_cookie();
log_message('debug', 'CSRF token verified');
return $this;
}
// --------------------------------------------------------------------
/**
* 设置csrf的cookie值
*/
public function csrf_set_cookie()
{
$expire = time() $this->_csrf_expire;
$secure_cookie = (config_item('cookie_secure') === TRUE) ? 1 : 0;
if ($secure_cookie && (empty($_SERVER['HTTPS']) OR strtolower($_SERVER['HTTPS']) === 'off'))
{
返回FALSE;
}
setcookie($this->_csrf_cookie_name, $this->_csrf_hash, $expire, config_item('cookie_path'), config_item('cookie_domain'), $secure_cookie);
log_message('debug', "CRSF cookie 设置");
返回 $this;
}
//csrf保存
公共函数 csrf_show_error()
{
show_error('不允许您请求的操作。');
}
//获取csrf的hash值
公共函数 get_csrf_hash()
{
返回 $this->_csrf_hash;
}
//获取csrf的token值
公共函数 get_csrf_token_name()
{
返回 $this->_csrf_token_name;
}
/**
* XSS 过滤
*/
公共函数 xss_clean($str, $is_image = FALSE)
{
// 是否是备份
if (is_array($str))
{
while (list($key) = every($str))
{
$str[$key] = $this->xss_clean($str[$key]);
}
返回 $str;
}
//去掉可见字符串
$str = remove_invisible_characters($str);
// 验证实体url
$str = $this->_validate_entities($str);
/*
* URL 解码
*
* 以防万一提交这样的东西:
*
* Google
*
* 注意:使用 rawurldecode() 所以它不会删除加号
*
*/
$str = rawurldecode($str);
/*
* 将字符实体转换为 ASCII
*
* 这使得我们下面的测试能够可靠地工作。
* 我们只转换标签内的实体
* 这些是会带来安全问题的。
*
*/
$str = preg_replace_callback("/[a-z] =(['"]).*?\1/si", array($this, '_convert_attribute'), $str);
$str = preg_replace_callback("/
* 再次删除隐形字符!
*/
$str = remove_invisible_characters($str);
/*
* 将所有制表符转换为空格
*
* 这可以防止这样的字符串:ja vascript
* 注意:我们稍后会处理字符之间的空格。
* 注意:发现 preg_replace 在
上速度慢得惊人
* 大块数据,所以我们使用str_replace。
*/
if (strpos($str, "t") !== FALSE)
{
$str = str_replace("t", ' ', $str);
}
/*
* 捕获转换后的字符串以便稍后比较
*/
$converted_string = $str;
// 删除不允许的字符串
$str = $this->_do_never_allowed($str);
/*
* 使 PHP 标签安全
*
* 注意:XML 标签也会被无意中替换:
*
*
*
* 不过好像没啥问题。
*/
if ($is_image === TRUE)
{
// 图像有 PHP 短开头的倾向和
// 经常关闭标签,所以我们跳过这些并且仅
// 做长的开始标签。
$str = preg_replace('/(php)/i', "\1", $str);
}
其他
{
$str = str_replace(array('', '?'.'>'), array('', '?>'), $str);
}
/*
* 压缩任何分解的单词
*
* 这更正了以下单词: j a v a s c r i p t
* 这些单词被压缩回到正确的状态。
*/
$words = 数组(
'javascript', '表达式', 'vbscript', '脚本', 'base64',
'小程序'、'警报'、'文档'、'写入'、'cookie'、'窗口'
);
foreach($words 作为 $word)
{
$temp = '';
for ($i = 0, $wordlen = strlen($word); $i
{
$temp .= substr($word, $i, 1)."s*";
}
// 我们只想在后面跟着非单词字符
时执行此操作
// 这样,像“dealer to”这样的有效内容就不会变成“dealerto”
$str = preg_replace_callback('#('.substr($temp, 0, -3).')(W)#is', array($this, '_compact_exploded_words'), $str);
}
/*
* 删除链接或 img 标签中不允许的 Javascript
* 我们曾经为PHP5做过一些版本比较和stripos的使用,
* 但与这些简化的非捕获相比,它太慢了
* preg_match(),特别是如果字符串中存在模式
*/
做
{
$original = $str;
if (preg_match("/
{
$str = preg_replace_callback("#
}
if (preg_match("/
{
$str = preg_replace_callback("#]*?)(s?/?>|$)#si", array($this, '_js_img_removal'), $str);
}
if (preg_match("/script/i", $str) OR preg_match("/xss/i", $str))
{
$str = preg_replace("##si", '[已删除]', $str);
}
}
while($original!=$str);
未设置($original);
// 移除 style、onclick 和 xmlns
等邪恶属性
$str = $this->_remove_evil_attributes($str, $is_image);
/*
* 清理顽皮的 HTML 元素
*
* 如果标签包含列表中的任何单词
* 找到下面,标签被转换为实体。
*
* 所以这样:
* 变成:
*/
$naughty = 'alert|applet|audio|basefont|base|behavior|bgsound|blink|body|embed|表达式|form|frameset|frame|head|html|ilayer|iframe|input|isindex|layer|link|meta|对象|纯文本|样式|脚本|文本区域|标题|视频|xml|xss';
$str = preg_replace_callback('#
/*
* 清理顽皮的脚本元素
*
* 与上面类似,只是不寻找
* 标记它寻找 PHP 和 JavaScript 命令
* 是不允许的。 而不是删除
* 代码,它只是将括号转换为实体
* 使代码不可执行。
*
* 例如:eval('一些代码')
* 变成: eval('一些代码')
*/
$str = preg_replace('#(alert|cmd|passthru|eval|exec|表达式|system|fopen|fsockopen|file|file_get_contents|readfile|unlink)(s*)((.*?))#si', " \1\2(\3)", $str);
// 最终清理
// 这增加了一些额外的预防措施,以防万一
// 通过上述过滤器的东西
$str = $this->_do_never_allowed($str);
/*
* 图像以特殊方式处理
* - 本质上,我们想知道在所有角色之后
* 无论是否发现任何不需要的(可能是 XSS)代码,都会完成转换。
* 如果不是,我们返回 TRUE,因为图像是干净的。
* 但是,如果转换后的字符串与
不匹配
* 删除 XSS 后的字符串,然后失败,因为存在不需要的 XSS
* 在处理过程中发现并删除/更改了代码。
*/
if ($is_image === TRUE)
{
返回($str == $converted_string)?正确:错误;
}
log_message('debug', "XSS 过滤完成");
return $str;
}
// --------------------------------------------------------------------
//保护url的随机hash值
public function xss_hash()
{
if ($this->_xss_hash == '')
{
mt_srand();
$this->_xss_hash = md5(time() mt_rand(0, 1999999999));
}
return $this->_xss_hash;
}
// --------------------------------------------------------------------
/**
* html实体转码
*/
public function entity_decode($str, $charset='UTF-8')
{
if (stristr($str, '&') === FALSE)
{
return $str;
}
$str = html_entity_decode($str, ENT_COMPAT, $charset);
$str = preg_replace('~(0*[0-9a-f]{2,5})~ei', 'chr(hexdec("\1"))', $str);
return preg_replace('~([0-9]{2,4})~e', 'chr(\1)', $str);
}
// --------------------------------------------------------------------
//过滤文件名,保证文件名安全
public function sanitize_filename($str, $relative_path = FALSE)
{
$bad = array(
"../",
"",
"<",
">",
"'",
'"',
'&',
'$',
'#',
'{',
'}',
'[',
']',
'=',
';',
'?',
" ",
""",
"
"", // >
"(", // (
")", // )
"%28", // (
"&", // &
"$", // $
"?", // ?
";", // ;
"=" // =
);
if ( ! $relative_path)
{
$bad[] = './';
$bad[] = '/';
}
$str = remove_invisible_characters($str, FALSE);
return stripslashes(str_replace($bad, '', $str));
}
//压缩单词如j a v a s c r i p t成javascript
protected function _compact_exploded_words($matches)
{
return preg_replace('/s /s', '', $matches[1]).$matches[2];
}
// --------------------------------------------------------------------
/*
* 去掉一些危害的html属性
*/
protected function _remove_evil_attributes($str, $is_image)
{
// All javascript event handlers (e.g. onload, onclick, onmouseover), style, and xmlns
$evil_attributes = array('onw*', 'style', 'xmlns', 'formaction');
if ($is_image === TRUE)
{
/*
* Adobe Photoshop puts XML metadata into JFIF images,
* including namespacing, so we have to allow this for images.
*/
unset($evil_attributes[array_search('xmlns', $evil_attributes)]);
}
do {
$count = 0;
$attribs = array();
// find occurrences of illegal attribute strings with quotes (042 and 047 are octal quotes)
preg_match_all('/('.implode('|', $evil_attributes).')s*=s*(