CI框架安全类Security.php源码分析


Posted in PHP onNovember 04, 2014

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脚本标签强制转成实体对象

<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

/**

 * 安全类

 */

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]',

  '<!--'    => '<!--',

  '-->'    => '-->',

  '<![CDATA['   => '<![CDATA[',

  '<comment>'   => '<comment>'

 );

 //不允许出现的正则表达式数组

 protected $_never_allowed_regex = array(

  'javascript\s*:',

  'expression\s*(\(|&\#40;)', // CSS and IE

  'vbscript\s*:', // IE, surprise!

  'Redirect\s+302',

  "([\"'])?data\s*:[^\\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");

 }

 // --------------------------------------------------------------------

 /**

  * Verify Cross Site Request Forgery Protection

  *

  * @return object

  */

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

  {

   return 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 Set");

  return $this;

 }

 //csrf保存

 public function csrf_show_error()

 {

  show_error('The action you have requested is not allowed.');

 }

 //获取csrf的hash值

 public function get_csrf_hash()

 {

  return $this->_csrf_hash;

 }

 //获取csrf的token值

 public function get_csrf_token_name()

 {

  return $this->_csrf_token_name;

 }

 /**

  * XSS 过滤

  */

 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

  $str = $this->_validate_entities($str);

  /*

   * URL 解码

   *

   * Just in case stuff like this is submitted:

   *

   * <a href="http://%77%77%77%2E%67%6F%6F%67%6C%65%2E%63%6F%6D">Google</a>

   *

   * Note: Use rawurldecode() so it does not remove plus signs

   *

   */

  $str = rawurldecode($str);

  /*

   * Convert character entities to ASCII

   *

   * This permits our tests below to work reliably.

   * We only convert entities that are within tags since

   * these are the ones that will pose security problems.

   *

   */

  $str = preg_replace_callback("/[a-z]+=([\'\"]).*?\\1/si", array($this, '_convert_attribute'), $str);

  $str = preg_replace_callback("/<\w+.*?(?=>|<|$)/si", array($this, '_decode_entity'), $str);

  /*

   * Remove Invisible Characters Again!

   */

  $str = remove_invisible_characters($str);

  /*

   * Convert all tabs to spaces

   *

   * This prevents strings like this: ja vascript

   * NOTE: we deal with spaces between characters later.

   * NOTE: preg_replace was found to be amazingly slow here on

   * large blocks of data, so we use str_replace.

   */

  if (strpos($str, "\t") !== FALSE)

  {

   $str = str_replace("\t", ' ', $str);

  }

  /*

   * Capture converted string for later comparison

   */

  $converted_string = $str;

  // Remove Strings that are never allowed

  $str = $this->_do_never_allowed($str);

  /*

   * Makes PHP tags safe

   *

   * Note: XML tags are inadvertently replaced too:

   *

   * <?xml

   *

   * But it doesn't seem to pose a problem.

   */

  if ($is_image === TRUE)

  {

   // Images have a tendency to have the PHP short opening and

   // closing tags every so often so we skip those and only

   // do the long opening tags.

   $str = preg_replace('/<\?(php)/i', "<?\\1", $str);

  }

  else

  {

   $str = str_replace(array('<?', '?'.'>'),  array('<?', '?>'), $str);

  }

  /*

   * Compact any exploded words

   *

   * This corrects words like:  j a v a s c r i p t

   * These words are compacted back to their correct state.

   */

  $words = array(

   'javascript', 'expression', 'vbscript', 'script', 'base64',

   'applet', 'alert', 'document', 'write', 'cookie', 'window'

  );

  foreach ($words as $word)

  {

   $temp = '';

   for ($i = 0, $wordlen = strlen($word); $i < $wordlen; $i++)

   {

    $temp .= substr($word, $i, 1)."\s*";

   }

   // We only want to do this when it is followed by a non-word character

   // That way valid stuff like "dealer to" does not become "dealerto"

   $str = preg_replace_callback('#('.substr($temp, 0, -3).')(\W)#is', array($this, '_compact_exploded_words'), $str);

  }

  /*

   * Remove disallowed Javascript in links or img tags

   * We used to do some version comparisons and use of stripos for PHP5,

   * but it is dog slow compared to these simplified non-capturing

   * preg_match(), especially if the pattern exists in the string

   */

  do

  {

   $original = $str;

   if (preg_match("/<a/i", $str))

   {

    $str = preg_replace_callback("#<a\s+([^>]*?)(>|$)#si", array($this, '_js_link_removal'), $str);

   }

   if (preg_match("/<img/i", $str))

   {

    $str = preg_replace_callback("#<img\s+([^>]*?)(\s?/?>|$)#si", array($this, '_js_img_removal'), $str);

   }

   if (preg_match("/script/i", $str) OR preg_match("/xss/i", $str))

   {

    $str = preg_replace("#<(/*)(script|xss)(.*?)\>#si", '[removed]', $str);

   }

  }

  while($original != $str);

  unset($original);

  // Remove evil attributes such as style, onclick and xmlns

  $str = $this->_remove_evil_attributes($str, $is_image);

  /*

   * Sanitize naughty HTML elements

   *

   * If a tag containing any of the words in the list

   * below is found, the tag gets converted to entities.

   *

   * So this: <blink>

   * Becomes: <blink>

   */

  $naughty = 'alert|applet|audio|basefont|base|behavior|bgsound|blink|body|embed|expression|form|frameset|frame|head|html|ilayer|iframe|input|isindex|layer|link|meta|object|plaintext|style|script|textarea|title|video|xml|xss';

  $str = preg_replace_callback('#<(/*\s*)('.$naughty.')([^><]*)([><]*)#is', array($this, '_sanitize_naughty_html'), $str);

  /*

   * Sanitize naughty scripting elements

   *

   * Similar to above, only instead of looking for

   * tags it looks for PHP and JavaScript commands

   * that are disallowed.  Rather than removing the

   * code, it simply converts the parenthesis to entities

   * rendering the code un-executable.

   *

   * For example: eval('some code')

   * Becomes:  eval('some code')

   */

  $str = preg_replace('#(alert|cmd|passthru|eval|exec|expression|system|fopen|fsockopen|file|file_get_contents|readfile|unlink)(\s*)\((.*?)\)#si', "\\1\\2(\\3)", $str);

  // Final clean up

  // This adds a bit of extra precaution in case

  // something got through the above filters

  $str = $this->_do_never_allowed($str);

  /*

   * Images are Handled in a Special Way

   * - Essentially, we want to know that after all of the character

   * conversion is done whether any unwanted, likely XSS, code was found.

   * If not, we return TRUE, as the image is clean.

   * However, if the string post-conversion does not matched the

   * string post-removal of XSS, then it fails, as there was unwanted XSS

   * code found and removed/changed during processing.

   */

  if ($is_image === TRUE)

  {

   return ($str == $converted_string) ? TRUE: FALSE;

  }

  log_message('debug', "XSS Filtering completed");

  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('~&#x(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(

   "../",

   "<!--",

   "-->",

   "<",

   ">",

   "'",

   '"',

   '&',

   '$',

   '#',

   '{',

   '}',

   '[',

   ']',

   '=',

   ';',

   '?',

   "%20",

   "%22",

   "%3c",  // <

   "%253c", // <

   "%3e",  // >

   "%0e",  // >

   "%28",  // (

   "%29",  // )

   "%2528", // (

   "%26",  // &

   "%24",  // $

   "%3f",  // ?

   "%3b",  // ;

   "%3d"  // =

  );

  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('on\w*', '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*(\042|\047)([^\\2]*?)(\\2)/is', $str, $matches, PREG_SET_ORDER);

   foreach ($matches as $attr)

   {

    $attribs[] = preg_quote($attr[0], '/');

   }

   // find occurrences of illegal attribute strings without quotes

   preg_match_all('/('.implode('|', $evil_attributes).')\s*=\s*([^\s>]*)/is', $str, $matches, PREG_SET_ORDER);

   foreach ($matches as $attr)

   {

    $attribs[] = preg_quote($attr[0], '/');

   }

   // replace illegal attribute strings that are inside an html tag

   if (count($attribs) > 0)

   {

    $str = preg_replace('/(<?)(\/?[^><]+?)([^A-Za-z<>\-])(.*?)('.implode('|', $attribs).')(.*?)([\s><]?)([><]*)/i', '$1$2 $4$6$7$8', $str, -1, $count);

   }

  } while ($count);

  return $str;

 }

 // --------------------------------------------------------------------

 /**

  * 净化html,补齐未关闭的标签

  */

 protected function _sanitize_naughty_html($matches)

 {

  // encode opening brace

  $str = '<'.$matches[1].$matches[2].$matches[3];

  // encode captured opening or closing brace to prevent recursive vectors

  $str .= str_replace(array('>', '<'), array('>', '<'),

       $matches[4]);

  return $str;

 }

 // --------------------------------------------------------------------

 /**

  * 过滤超链接中js

  */

 protected function _js_link_removal($match)

 {

  return str_replace(

   $match[1],

   preg_replace(

    '#href=.*?(alert\(|alert&\#40;|javascript\:|livescript\:|mocha\:|charset\=|window\.|document\.|\.cookie|<script|<xss|data\s*:)#si',

    '',

    $this->_filter_attributes(str_replace(array('<', '>'), '', $match[1]))

   ),

   $match[0]

  );

 }

 // --------------------------------------------------------------------

 /**

  * 过滤图片链接中的js

  */

 protected function _js_img_removal($match)

 {

  return str_replace(

   $match[1],

   preg_replace(

    '#src=.*?(alert\(|alert&\#40;|javascript\:|livescript\:|mocha\:|charset\=|window\.|document\.|\.cookie|<script|<xss|base64\s*,)#si',

    '',

    $this->_filter_attributes(str_replace(array('<', '>'), '', $match[1]))

   ),

   $match[0]

  );

 }

 // --------------------------------------------------------------------

 /**

  * 转换属性,将一些字符转换成实体

  */

 protected function _convert_attribute($match)

 {

  return str_replace(array('>', '<', '\\'), array('>', '<', '\\\\'), $match[0]);

 }

 // --------------------------------------------------------------------

 //过滤html标签属性

 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实体转码

 protected function _decode_entity($match)

 {

  return $this->entity_decode($match[0], strtoupper(config_item('charset')));

 }

 // --------------------------------------------------------------------

 /**

  * 验证url实体

  */

 protected function _validate_entities($str)

 {

  /*

   * Protect GET variables in URLs

   */

   // 901119URL5918AMP18930PROTECT8198

  $str = preg_replace('|\&([a-z\_0-9\-]+)\=([a-z\_0-9\-]+)|i', $this->xss_hash()."\\1=\\2", $str);

  /*

   * Validate standard character entities

   *

   * Add a semicolon if missing.  We do this to enable

   * the conversion of entities to ASCII later.

   *

   */

  $str = preg_replace('#(&\#?[0-9a-z]{2,})([\x00-\x20])*;?#i', "\\1;\\2", $str);

  /*

   * Validate UTF16 two byte encoding (x00)

   *

   * Just as above, adds a semicolon if missing.

   *

   */

  $str = preg_replace('#(&\#x?)([0-9A-F]+);?#i',"\\1\\2;",$str);

  /*

   * Un-Protect GET variables in URLs

   */

  $str = str_replace($this->xss_hash(), '&', $str);

  return $str;

 }

 // ----------------------------------------------------------------------

 //过滤不允许出现的字符串

 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的hash值

 protected function _csrf_set_hash()

 {

  if ($this->_csrf_hash == '')

  {

   // 如果_csrf_cookie_name存在,直接作为csrf hash值

   if (isset($_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];

   }

                        //否则随机一个md5字符串

   return $this->_csrf_hash = md5(uniqid(rand(), TRUE));

  }

  return $this->_csrf_hash;

 }

}
PHP 相关文章推荐
用Zend Encode编写开发PHP程序
Oct 09 PHP
PHP中MVC模式的模板引擎开发经验分享
Mar 23 PHP
PHP中通过语义URL防止网站被攻击的方法分享
Sep 08 PHP
PHP中strtotime函数使用方法详解
Nov 27 PHP
php 获取百度的热词数据的代码
Feb 18 PHP
PHP utf-8编码问题,utf8编码,数据库乱码,页面显示输出乱码
Apr 08 PHP
Php中用PDO查询Mysql来避免SQL注入风险的方法
Apr 25 PHP
php遍历删除整个目录及文件的方法
Mar 13 PHP
php实现数组按指定KEY排序的方法
Mar 30 PHP
PHP实现的简单操作SQLite数据库类与用法示例
Jun 19 PHP
thinkphp5.1框架中容器(Container)和门面(Facade)的实现方法分析
Aug 05 PHP
php操作redis命令及代码实例大全
Nov 19 PHP
CI框架Session.php源码分析
Nov 03 #PHP
PHP has encountered a Stack overflow问题解决方法
Nov 03 #PHP
完美实现wordpress禁止文章修订和自动保存的方法
Nov 03 #PHP
php中使用Ajax时出现Error(c00ce56e)的详细解决方案
Nov 03 #PHP
PHP防止注入攻击实例分析
Nov 03 #PHP
自编函数解决pathinfo()函数处理中文问题
Nov 03 #PHP
php基于base64解码图片与加密图片还原实例
Nov 03 #PHP
You might like
深入解析PHP中的(伪)多线程与多进程
2013/07/01 PHP
PHP imagegrabscreen和imagegrabwindow(截取网站缩略图)的实例代码
2013/11/07 PHP
PHP实现的简单三角形、矩形周长面积计算器分享
2014/11/18 PHP
php实现可逆加密的方法
2015/08/11 PHP
php中让人头疼的浮点数运算分析
2016/10/10 PHP
thinkphp 手机号和用户名同时登录
2017/01/20 PHP
PHP实现笛卡尔积算法的实例讲解
2019/12/22 PHP
PHP 8新特性简介
2020/08/18 PHP
关于jQuery的inArray 方法介绍
2011/10/08 Javascript
JavaScript String.replace函数参数实例说明
2013/06/06 Javascript
JavaScript中${pageContext.request.contextPath}取值问题及解决方案
2016/12/08 Javascript
浅述节点的创建及常见功能的实现
2016/12/15 Javascript
JS编写函数实现对身份证号码最后一位的验证功能
2016/12/29 Javascript
Angularjs使用ng-repeat中$even和$odd属性的注意事项
2016/12/31 Javascript
使用AngularJS对表单提交内容进行验证的操作方法
2017/07/12 Javascript
详解Vue 事件修饰符capture 的使用
2017/12/29 Javascript
从vue基础开始创建一个简单的增删改查的实例代码(推荐)
2018/02/11 Javascript
微信小程序入门之广告条实现方法示例
2018/12/05 Javascript
微信小程序绘制图片发送朋友圈
2019/07/25 Javascript
vue2.0+SVG实现音乐播放圆形进度条组件
2019/09/21 Javascript
Vue.js组件使用props传递数据的方法
2019/10/19 Javascript
javascript实现智能手环时间显示
2020/09/18 Javascript
[原创]python爬虫(入门教程、视频教程)
2018/01/08 Python
Python 获得命令行参数的方法(推荐)
2018/01/24 Python
Python docx库用法示例分析
2019/02/16 Python
python爬取内容存入Excel实例
2019/02/20 Python
python matplotlib拟合直线的实现
2019/11/19 Python
基于HTML5 WebGL的3D机房的示例
2018/03/16 HTML / CSS
详解Canvas实用库Fabric.js使用手册
2019/01/07 HTML / CSS
房地产融资计划书
2014/01/10 职场文书
幼儿园家长反馈意见
2015/06/03 职场文书
工作一年自我鉴定
2019/06/20 职场文书
导游词之襄阳古城
2019/09/27 职场文书
Oracle 数据仓库ETL技术之多表插入语句的示例详解
2021/04/12 Oracle
《勇者辞职不干了》上卷BD发售宣传CM公开
2022/04/08 日漫
React更新渲染原理深入分析
2022/12/24 Javascript