对CI_Security类的注释

来源:互联网 发布:php 重复的数据相加 编辑:程序博客网 时间:2024/05/21 06:40
<?php
defined('BASEPATH') OR exit('No direct script access allowed');


//安全类
class CI_Security {


/**
* 需要清理的字符
*
* @var array
*/
public $filename_bad_chars =array(
'../', '<!--', '-->', '<', '>',
"'", '"', '&', '$', '#',
'{', '}', '[', ']', '=',
';', '?', '%20', '%22',
'%3c', // <
'%253c', // <
'%3e', // >
'%0e', // >
'%28', // (
'%29', // )
'%2528', // (
'%26', // &
'%24', // $
'%3f', // ?
'%3b', // ;
'%3d' // =
);


/**
* 字符集
*
* @var string
*/
public $charset = 'UTF-8';


/**
* 为了保护URL构造的哈希值
*
* @var string
*/
protected $_xss_hash;


/**
* 预防CSRF的cookie名称
*
* @var string
*/
protected $_csrf_hash;


/**
* 预防CSRF的失效时间
*
* @var int
*/
protected $_csrf_expire =7200;


/**
* 预防CSRF的token名称
*
* @var string
*/
protected $_csrf_token_name ='ci_csrf_token';


/**
* 预防CSRF的cookie名称
*
* @var string
*/
protected $_csrf_cookie_name ='ci_csrf_token';


/**
* 不允许出现的字符串
*
* @var array
*/
protected $_never_allowed_str =array(
'document.cookie'=> '[removed]',
'document.write'=> '[removed]',
'.parentNode' => '[removed]',
'.innerHTML' => '[removed]',
'-moz-binding'=> '[removed]',
'<!--' => '&lt;!--',
'-->' => '--&gt;',
'<![CDATA[' => '&lt;![CDATA[',
'<comment>' => '&lt;comment&gt;'
);


/**
* 不允许的正则替换列表
*
* @var array
*/
protected $_never_allowed_regex = array(
'javascript\s*:',
'(document|(document\.)?window)\.(location|on\w*)',
'expression\s*(\(|&\#40;)', // CSS and IE
'vbscript\s*:', // IE, surprise!
'wscript\s*:', // IE
'jscript\s*:', // IE
'vbs\s*:', // IE
'Redirect\s+30\d',
"([\"'])?data\s*:[^\\1]*?base64[^\\1]*?,[^\\1]*?\\1?"
);


/**
* 构造函数
*
* @return void
*/
public function __construct(){
//是否开启csrf保护
if (config_item('csrf_protection')){
foreach (array('csrf_expire', 'csrf_token_name', 'csrf_cookie_name') as $key){
if (NULL !== ($val = config_item($key))){
$this->{'_'.$key} = $val;
}
}


//是否添加前缀
if ($cookie_prefix = config_item('cookie_prefix')){
$this->_csrf_cookie_name = $cookie_prefix.$this->_csrf_cookie_name;
}


//设置CSRF哈希值
$this->_csrf_set_hash();
}


$this->charset = strtoupper(config_item('charset'));


log_message('info', 'Security Class Initialized');
}




/**
* CSRF验证
*
* @return CI_Security
*/
public function csrf_verify(){
//如果它不是POST过来的请求,我们会设置CSRF的cookie
if (strtoupper($_SERVER['REQUEST_METHOD']) !== 'POST'){
return $this->csrf_set_cookie();
}


//检测该URI是否在CSRF检测的白名单中
if ($exclude_uris = config_item('csrf_exclude_uris')){
$uri = load_class('URI', 'core');
foreach ($exclude_uris as $excluded){
if (preg_match('#^'.$excluded.'$#i'.(UTF8_ENABLED ? 'u' : ''), $uri->uri_string())){
return $this;
}
}
}


//判断$_POST和$_COOKIE中的数据
if ( ! isset($_POST[$this->_csrf_token_name], $_COOKIE[$this->_csrf_cookie_name])
OR $_POST[$this->_csrf_token_name] !== $_COOKIE[$this->_csrf_cookie_name]) {
$this->csrf_show_error();
}


//我们会清理掉它,而且我们不想污染$_POST数组
unset($_POST[$this->_csrf_token_name]);


//每次提交之后都会重新生成
if (config_item('csrf_regenerate')){
//不可能一直存在下去
unset($_COOKIE[$this->_csrf_cookie_name]);
$this->_csrf_hash = NULL;
}


$this->_csrf_set_hash();
$this->csrf_set_cookie();


log_message('info', 'CSRF token verified');
return $this;
}



/**
* 设置CSRF的cookie值
*
* @return CI_Security
*/
public function csrf_set_cookie(){
$expire = time() + $this->_csrf_expire;
$secure_cookie = (bool) config_item('cookie_secure');


if ($secure_cookie && ! is_https()){
return FALSE;
}


setcookie(
$this->_csrf_cookie_name,
$this->_csrf_hash,
$expire,
config_item('cookie_path'),
config_item('cookie_domain'),
$secure_cookie,
config_item('cookie_httponly')
);
log_message('info', 'CRSF cookie sent');


return $this;
}



/**
* 展示CSRF错误
*
* @return void
*/
public function csrf_show_error(){
show_error('The action you have requested is not allowed.', 403);
}



/**
* 获取CSRF哈希值
*
* @return string哈希值
*/
public function get_csrf_hash(){
return $this->_csrf_hash;
}



/**
* 获取CSRF的令牌环名称
*
* @return string 令牌环名称
*/
public function get_csrf_token_name(){
return $this->_csrf_token_name;
}



/**
* XSS清理
* 它做了相当数量的工作来预防XSS注入
* 但是没有事情是100%可靠的
*
* @param string|string[] $str输入数据
* @param bool$is_image    是否是一个图片
* @return string
*/
public function xss_clean($str, $is_image = FALSE){
//是否是数组
if (is_array($str)){
while (list($key) = each($str)){
$str[$key] = $this->xss_clean($str[$key]);
}


return $str;
}


//移除不可见字符
$str = remove_invisible_characters($str);


//URL解码,就像下面的数据被提交:
//<a href="http://%77%77%77%2E%67%6F%6F%67%6C%65%2E%63%6F%6D">Google</a>
//rawurldecode()不会把加号(+)解码为空格
//urldecode()会把加号(+)解码为空格
do{
$str = rawurldecode($str);
}while (preg_match('/%[0-9a-f]{2,}/i', $str));


//转换字符实体到ASCII码
//我们只转换字符实体,是因为它们会导致问题
$str = preg_replace_callback("/[^a-z0-9>]+[a-z0-9]+=([\'\"]).*?\\1/si", array($this, '_convert_attribute'), $str);
$str = preg_replace_callback('/<\w+.*/si', array($this, '_decode_entity'), $str);


//再次移除不可见字符
$str = remove_invisible_characters($str);


//把所有的制表符换成空格
//它会阻止这样的:ja vascript
//在处理大段的数据的时候
//preg_replace()比str_replace()慢很多
$str = str_replace("\t", ' ', $str);


//获取转换后的字符串用来一会儿做比较
$converted_string = $str;


//移除禁止的字符信息
$str = $this->_do_never_allowed($str);


//替换PHP的开始定界符
//它也会替换xml的开始标签<?xml
//但是通常不会导致问题
if ($is_image === TRUE){
$str = preg_replace('/<\?(php)/i', '&lt;?\\1', $str);
}else{
$str = str_replace(array('<?', '?'.'>'), array('&lt;?', '?&gt;'), $str);
}


//压缩被展开的单词,比如j a v a s c r i p t
//这些单词会变成它们正常的状态
$words = array(
'javascript', 'expression', 'vbscript', 'jscript', 'wscript',
'vbs', 'script', 'base64', 'applet', 'alert', 'document',
'write', 'cookie', 'window', 'confirm', 'prompt'
);


foreach ($words as $word){
$word = implode('\s*', str_split($word)).'\s*';




//它这里只是在后面跟一个非单词的时候
//比如"dealer to"不会变成"dealerto"
$str = preg_replace_callback('#('.substr($word, 0, -3).')(\W)#is', array($this, '_compact_exploded_words'), $str);
}




//在超链接标签和图片标签中移除非法的JavaScript
//在当前情况下,stripos()比preg_match()要慢
//不仅仅是空格,下面的规则都可能会导致问题:
//[\d\s"\'`;,\/\=\(\x00\x0B\x09\x0C]
//由于remove_invisible_characters()已经清除了十六进制格式的内容
//因此我们这里就跳过了
do{
$original = $str;


if (preg_match('/<a/i', $str)){
$str = preg_replace_callback('#<a[^a-z0-9>]+([^>]*?)(?:>|$)#si', array($this, '_js_link_removal'), $str);
}


if (preg_match('/<img/i', $str)){
$str = preg_replace_callback('#<img[^a-z0-9]+([^>]*?)(?:\s?/?>|$)#si', array($this, '_js_img_removal'), $str);
}


if (preg_match('/script|xss/i', $str)){
$str = preg_replace('#</*(?:script|xss).*?>#si', '[removed]', $str);
}
}while ($original !== $str);


unset($original);


//移除可执行的属性,比如style、onclick、xmlns
$str = $this->_remove_evil_attributes($str, $is_image);


//清理非法的HTML元素,如果一个标签包含列表中的任何一个单词
//标签就会转换为实体,即<blink>变成&lt;blink&gt;
$naughty = 'alert|prompt|confirm|applet|audio|basefont|base|behavior|bgsound|blink|body|embed|expression|form|frameset|frame|head|html|ilayer|iframe|input|button|select|isindex|layer|link|meta|keygen|object|plaintext|style|script|textarea|title|math|video|svg|xml|xss';
$str = preg_replace_callback('#<(/*\s*)('.$naughty.')([^><]*)([><]*)#is', array($this, '_sanitize_naughty_html'), $str);


//清理非法的脚本元素,它只是寻找PHP和JavaScript禁止的命令
//它不是移除整个代码,它只是进行转换来让代码不要运行
//比如eval('some code')会变成eval&#40;'some code'&#41;
$str = preg_replace('#(alert|prompt|confirm|cmd|passthru|eval|exec|expression|system|fopen|fsockopen|file|file_get_contents|readfile|unlink)(\s*)\((.*?)\)#si',
'\\1\\2&#40;\\3&#41;',
$str);


//最终的清理
$str = $this->_do_never_allowed($str);

//图片稍微特殊一些
if ($is_image === TRUE){
return ($str === $converted_string);
}


return $str;
}



/**
* 生成用于处理XSS的哈希值
*
* @return string XSS hash
*/
public function xss_hash(){
if ($this->_xss_hash === NULL){
$rand = $this->get_random_bytes(16);
$this->_xss_hash = ($rand === FALSE)
? md5(uniqid(mt_rand(), TRUE))
: bin2hex($rand);
}


return $this->_xss_hash;
}



/**
* 获取随机字符
*
* @param int $length  输出的长度
* @return string
*/
public function get_random_bytes($length){
if (empty($length) OR ! ctype_digit((string) $length)){
return FALSE;
}


//不幸的是,下面是否存在都是不确定的
if (defined('MCRYPT_DEV_URANDOM') && ($output = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM)) !== FALSE){
return $output;
}




if (is_readable('/dev/urandom') && ($fp = fopen('/dev/urandom', 'rb')) !== FALSE){
is_php('5.4') && stream_set_chunk_size($fp, $length);
$output = fread($fp, $length);
fclose($fp);
if ($output !== FALSE){
return $output;
}
}


if (function_exists('openssl_random_pseudo_bytes')){
return openssl_random_pseudo_bytes($length);
}


return FALSE;
}



/**
* HTML字符实体解码
* 这里没有使用html_entity_decode()这个函数
* 因为有些浏览器会忽略最后的分号

* @link http://php.net/html-entity-decode
*
* @param string $str Input
* @param string $charsetCharacter set
* @return string
*/
public function entity_decode($str, $charset = NULL){
if (strpos($str, '&') === FALSE){
return $str;
}


static $_entities;


isset($charset) OR $charset = $this->charset;
$flag = is_php('5.4') ? ENT_COMPAT | ENT_HTML5 : ENT_COMPAT;


do{
$str_compare = $str;


// 解码标准的字符实体
if (preg_match_all('/&[a-z]{2,}(?![a-z;])/i', $str, $matches)){
if ( ! isset($_entities)){
$_entities = array_map(
'strtolower',
is_php('5.3.4')
? get_html_translation_table(HTML_ENTITIES, $flag, $charset)
: get_html_translation_table(HTML_ENTITIES, $flag)
);


//如果当前版本不高于PHP5.4,添加HTML 5新加的字符实体到数组
if ($flag === ENT_COMPAT){
$_entities[':'] = '&colon;';
$_entities['('] = '&lpar;';
$_entities[')'] = '&rpar;';
$_entities["\n"] = '&newline;';
$_entities["\t"] = '&tab;';
}
}


$replace = array();
$matches = array_unique(array_map('strtolower', $matches[0]));
foreach ($matches as &$match){
if (($char = array_search($match.';', $_entities, TRUE)) !== FALSE){
$replace[$match] = $char;
}
}


$str = str_ireplace(array_keys($replace), array_values($replace), $str);
}


//执行解码操作
$str = html_entity_decode(
preg_replace('/(&#(?:x0*[0-9a-f]{2,5}(?![0-9a-f;])|(?:0*\d{2,4}(?![0-9;]))))/iS', '$1;', $str),
$flag,
$charset
);
} while ($str_compare !== $str);
return $str;
}



/**
* 清理文件名
*
* @param string $str     输入的文件名
* @param bool$relative_path 是否保持路径
* @return string
*/
public function sanitize_filename($str, $relative_path = FALSE){
$bad = $this->filename_bad_chars;


if ( ! $relative_path){
$bad[] = './';
$bad[] = '/';
}


$str = remove_invisible_characters($str, FALSE);


do{
$old = $str;
$str = str_replace($bad, '', $str);
}while ($old !== $str);


return stripslashes($str);
}



/**
* 处理图片标签
*
* @param string $str
* @return string
*/
public function strip_image_tags($str){
return preg_replace(array('#<img[\s/]+.*?src\s*=\s*["\'](.+?)["\'].*?\>#', '#<img[\s/]+.*?src\s*=\s*(.+?).*?\>#'), '\\1', $str);
}



/**
* 压缩字符串
* 被xss_clean()调用清理就像'j a v a s c r i p t'中的空格
*
* @param array $matches
* @return string
*/
protected function _compact_exploded_words($matches){
return preg_replace('/\s+/s', '', $matches[1]).$matches[2];
}



/**
* 移除可执行的HTML属性,比如事件监听和样式
* 它移除非法属性

* 可能是通过空格区分:
* <code>
* <a |style=document.write('hello');alert('world');| class=link>
* </code>
*
* 可能是通过引号区分:
* <code>
* <a |style="document.write('hello'); alert('world');"| class="link">
* </code>
*
* @param string $str 待检测的字符串
* @param bool $is_image是否是一个图片
* @return string            移除非法属性后的字符串
*/
protected function _remove_evil_attributes($str, $is_image){
$evil_attributes = array('on\w*', 'style', 'xmlns', 'formaction', 'form', 'xlink:href', 'FSCommand', 'seekSegmentTime');


if ($is_image === TRUE){
//由于Adobe Photoshop把XML metadata放到JFIF图片中
//因为它包含命名空间,因此我们需要允许xmlns
unset($evil_attributes[array_search('xmlns', $evil_attributes)]);
}


do {
$count = $temp_count = 0;


//移除有引号的非法属性(042和047是其八进制)
$str = preg_replace('/(<[^>]+)(?<!\w)('.implode('|', $evil_attributes).')\s*=\s*(\042|\047)([^\\2]*?)(\\2)/is', '$1[removed]', $str, -1, $temp_count);
$count += $temp_count;


//移除没有引号的非法属性
$str = preg_replace('/(<[^>]+)(?<!\w)('.implode('|', $evil_attributes).')\s*=\s*([^\s>]*)/is', '$1[removed]', $str, -1, $temp_count);
$count += $temp_count;
}while ($count);


return $str;
}


/**
* 清理非法的HTML元素
* 它胡被xss_clean()调用
*
* @param array $matches
* @return string
*/
protected function _sanitize_naughty_html($matches){
return '&lt;'.$matches[1].$matches[2].$matches[3].str_replace(array('>', '<'), array('&gt;', '&lt;'), $matches[4]);
}



/**
* 移除超链接的JS代码,它会被xss_clean()调用
*
* @param array $match
* @return string
*/
protected function _js_link_removal($match){
return str_replace($match[1],
preg_replace('#href=.*?(?:(?:alert|prompt|confirm)(?:\(|&\#40;)|javascript:|livescript:|mocha:|charset=|window\.|document\.|\.cookie|<script|<xss|data\s*:)#si',
'',
$this->_filter_attributes(str_replace(array('<', '>'), '', $match[1]))
),
$match[0]);
}



/**
* 去除图片中的JS代码,它会被xss_clean()调用
*
* @param array $match
* @return string
*/
protected function _js_img_removal($match){
return str_replace($match[1],
preg_replace('#src=.*?(?:(?:alert|prompt|confirm)(?:\(|&\#40;)|javascript:|livescript:|mocha:|charset=|window\.|document\.|\.cookie|<script|<xss|base64\s*,)#si',
'',
$this->_filter_attributes(str_replace(array('<', '>'), '', $match[1]))
),
$match[0]);
}



/**
* 属性转换
*
* @param array $match
* @return string
*/
protected function _convert_attribute($match){
return str_replace(array('>', '<', '\\'), array('&gt;', '&lt;', '\\\\'), $match[0]);
}



/**
* 过滤属性信息,主要为了一致性和安全性
*
* @param string $str
* @return string
*/
protected function _filter_attributes($str){
$out = '';
if (preg_match_all('#\s*[a-z\-]+\s*=\s*(\042|\047)([^\\1]*?)\\1#is', $str, $matches)){
foreach ($matches[0] as $match){
$out .= preg_replace('#/\*.*?\*/#s', '', $match);
}
}


return $out;
}



/**
* HTML字符解码时调用
*
* @param array $match
* @return string
*/
protected function _decode_entity($match){
$match = preg_replace('|\&([a-z\_0-9\-]+)\=([a-z\_0-9\-/]+)|i', $this->xss_hash().'\\1=\\2', $match[0]);


return str_replace(
$this->xss_hash(),
'&',
$this->entity_decode($match, $this->charset)
);
}



/**
* 不允许的字符信息
*
* @param string
* @return string
*/
protected function _do_never_allowed($str){
$str = str_replace(array_keys($this->_never_allowed_str), $this->_never_allowed_str, $str);


foreach ($this->_never_allowed_regex as $regex){
$str = preg_replace('#'.$regex.'#is', '[removed]', $str);
}


return $str;
}



/**
* 设置预防csrf的哈希值和cookie内容
*
* @return string
*/
protected function _csrf_set_hash(){
if ($this->_csrf_hash === NULL){
//如果该cookie已存在,我们可以使用这个值,就无需重新生成了
//有时候会存在一个页面中会存在一个子页面
if (isset($_COOKIE[$this->_csrf_cookie_name]) && is_string($_COOKIE[$this->_csrf_cookie_name])
&& preg_match('#^[0-9a-f]{32}$#iS', $_COOKIE[$this->_csrf_cookie_name]) === 1){
return $this->_csrf_hash = $_COOKIE[$this->_csrf_cookie_name];
}


$rand = $this->get_random_bytes(16);
$this->_csrf_hash = ($rand === FALSE)
? md5(uniqid(mt_rand(), TRUE))
: bin2hex($rand);
}


return $this->_csrf_hash;
}


}
0 0
原创粉丝点击