diff options
author | Joel Kallman <jkallman@eclarian.com> | 2012-03-12 23:10:44 +0100 |
---|---|---|
committer | Joel Kallman <jkallman@eclarian.com> | 2012-03-12 23:10:44 +0100 |
commit | 7c4870c3741e165bc7bc06624c816e66db474ee2 (patch) | |
tree | a96962212cb012be32ab09545dcd5c4dbdac05e2 /system/core/Security.php | |
parent | 10aa8e660c6f439958b79fce5d85ce7e8eecf028 (diff) | |
parent | 6b535f51fcb94e0a645fda0d0356f4748076877e (diff) |
Merge branch 'develop' into feature/db_toString_support
Diffstat (limited to 'system/core/Security.php')
-rwxr-xr-x | system/core/Security.php | 325 |
1 files changed, 132 insertions, 193 deletions
diff --git a/system/core/Security.php b/system/core/Security.php index ce3f7d3cc..cd8a61028 100755 --- a/system/core/Security.php +++ b/system/core/Security.php @@ -1,13 +1,13 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP 5.2.4 or newer * * NOTICE OF LICENSE - * + * * Licensed under the Open Software License version 3.0 - * + * * This source file is subject to the Open Software License (OSL 3.0) that is * bundled with this package in the files license.txt / license.rst. It is * also available through the world wide web at this URL: @@ -18,15 +18,13 @@ * * @package CodeIgniter * @author EllisLab Dev Team - * @copyright Copyright (c) 2008 - 2011, EllisLab, Inc. (http://ellislab.com/) + * @copyright Copyright (c) 2008 - 2012, EllisLab, Inc. (http://ellislab.com/) * @license http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) * @link http://codeigniter.com * @since Version 1.0 * @filesource */ -// ------------------------------------------------------------------------ - /** * Security Class * @@ -42,7 +40,6 @@ class CI_Security { * Random Hash for protecting URLs * * @var string - * @access protected */ protected $_xss_hash = ''; @@ -50,7 +47,6 @@ class CI_Security { * Random Hash for Cross Site Request Forgery Protection Cookie * * @var string - * @access protected */ protected $_csrf_hash = ''; @@ -59,7 +55,6 @@ class CI_Security { * Defaults to two hours (in seconds) * * @var int - * @access protected */ protected $_csrf_expire = 7200; @@ -67,7 +62,6 @@ class CI_Security { * Token name for Cross Site Request Forgery Protection Cookie * * @var string - * @access protected */ protected $_csrf_token_name = 'ci_csrf_token'; @@ -75,67 +69,64 @@ class CI_Security { * Cookie name for Cross Site Request Forgery Protection Cookie * * @var string - * @access protected */ - protected $_csrf_cookie_name = 'ci_csrf_token'; + protected $_csrf_cookie_name = 'ci_csrf_token'; /** * List of never allowed strings * * @var array - * @access protected */ - 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>' - ); + 'document.cookie' => '[removed]', + 'document.write' => '[removed]', + '.parentNode' => '[removed]', + '.innerHTML' => '[removed]', + 'window.location' => '[removed]', + '-moz-binding' => '[removed]', + '<!--' => '<!--', + '-->' => '-->', + '<![CDATA[' => '<![CDATA[', + '<comment>' => '<comment>' + ); /** * List of never allowed regex replacement * * @var array - * @access protected */ protected $_never_allowed_regex = array( - "javascript\s*:" => '[removed]', - "expression\s*(\(|&\#40;)" => '[removed]', // CSS and IE - "vbscript\s*:" => '[removed]', // IE, surprise! - "Redirect\s+302" => '[removed]' - ); + 'javascript\s*:', + 'expression\s*(\(|&\#40;)', // CSS and IE + 'vbscript\s*:', // IE, surprise! + 'Redirect\s+302' + ); - /** - * Constructor - */ public function __construct() { - // CSRF config - foreach(array('csrf_expire', 'csrf_token_name', 'csrf_cookie_name') as $key) + // Is CSRF protection enabled? + if (config_item('csrf_protection') === TRUE) { - if (FALSE !== ($val = config_item($key))) + // CSRF config + foreach (array('csrf_expire', 'csrf_token_name', 'csrf_cookie_name') as $key) { - $this->{'_'.$key} = $val; + if (FALSE !== ($val = config_item($key))) + { + $this->{'_'.$key} = $val; + } } - } - // Append application specific cookie prefix - if (config_item('cookie_prefix')) - { - $this->_csrf_cookie_name = config_item('cookie_prefix').$this->_csrf_cookie_name; - } + // Append application specific cookie prefix + if (config_item('cookie_prefix')) + { + $this->_csrf_cookie_name = config_item('cookie_prefix').$this->_csrf_cookie_name; + } - // Set the CSRF hash - $this->_csrf_set_hash(); + // Set the CSRF hash + $this->_csrf_set_hash(); + } - log_message('debug', "Security Class Initialized"); + log_message('debug', 'Security Class Initialized'); } // -------------------------------------------------------------------- @@ -147,8 +138,8 @@ class CI_Security { */ public function csrf_verify() { - // If no POST data exists we will set the CSRF cookie - if (count($_POST) == 0) + // If it's not a POST request we will set the CSRF cookie + if (strtoupper($_SERVER['REQUEST_METHOD']) !== 'POST') { return $this->csrf_set_cookie(); } @@ -164,30 +155,27 @@ class CI_Security { } // Do the tokens exist in both the _POST and _COOKIE arrays? - if ( ! isset($_POST[$this->_csrf_token_name]) OR - ! isset($_COOKIE[$this->_csrf_cookie_name])) + if ( ! isset($_POST[$this->_csrf_token_name]) OR ! isset($_COOKIE[$this->_csrf_cookie_name]) + OR $_POST[$this->_csrf_token_name] != $_COOKIE[$this->_csrf_cookie_name]) // Do the tokens match? { $this->csrf_show_error(); } - // Do the tokens match? - if ($_POST[$this->_csrf_token_name] != $_COOKIE[$this->_csrf_cookie_name]) + // We kill this since we're done and we don't want to polute the _POST array + unset($_POST[$this->_csrf_token_name]); + + // Regenerate on every submission? + if (config_item('csrf_regenerate')) { - $this->csrf_show_error(); + // Nothing should last forever + unset($_COOKIE[$this->_csrf_cookie_name]); + $this->_csrf_hash = ''; } - // 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_hash = ''; $this->_csrf_set_hash(); $this->csrf_set_cookie(); - log_message('debug', "CSRF token verified"); - + log_message('debug', 'CSRF token verified'); return $this; } @@ -203,19 +191,13 @@ class CI_Security { $expire = time() + $this->_csrf_expire; $secure_cookie = (bool) config_item('cookie_secure'); - if ($secure_cookie) + if ($secure_cookie && (empty($_SERVER['HTTPS']) OR strtolower($_SERVER['HTTPS']) === 'off')) { - $req = isset($_SERVER['HTTPS']) ? $_SERVER['HTTPS'] : FALSE; - - if ( ! $req OR $req == 'off') - { - return FALSE; - } + 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"); + log_message('debug', 'CRSF cookie Set'); return $this; } @@ -253,7 +235,7 @@ class CI_Security { * * Getter Method * - * @return string self::csrf_token_name + * @return string self::_csrf_token_name */ public function get_csrf_token_name() { @@ -273,7 +255,7 @@ class CI_Security { * the filter. * * Note: This function should only be used to deal with data - * upon submission. It's not something that should + * upon submission. It's not something that should * be used for general runtime processing. * * This function was based in part on some code and ideas I @@ -290,10 +272,7 @@ class CI_Security { */ public function xss_clean($str, $is_image = FALSE) { - /* - * Is the string an array? - * - */ + // Is the string an array? if (is_array($str)) { while (list($key) = each($str)) @@ -304,13 +283,8 @@ class CI_Security { return $str; } - /* - * Remove Invisible Characters - */ - $str = remove_invisible_characters($str); - - // Validate Entities in URLs - $str = $this->_validate_entities($str); + // Remove Invisible Characters and validate entities in URLs + $str = $this->_validate_entities(remove_invisible_characters($str)); /* * URL Decode @@ -320,7 +294,6 @@ class CI_Security { * <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); @@ -330,16 +303,11 @@ class CI_Security { * 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); - $str = preg_replace_callback("/<\w+.*?(?=>|<|$)/si", array($this, '_decode_entity'), $str); - - /* - * Remove Invisible Characters Again! - */ + // Remove Invisible Characters Again! $str = remove_invisible_characters($str); /* @@ -350,15 +318,9 @@ class CI_Security { * NOTE: preg_replace was found to be amazingly slow here on * large blocks of data, so we use str_replace. */ + $str = str_replace("\t", ' ', $str); - if (strpos($str, "\t") !== FALSE) - { - $str = str_replace("\t", ' ', $str); - } - - /* - * Capture converted string for later comparison - */ + // Capture converted string for later comparison $converted_string = $str; // Remove Strings that are never allowed @@ -378,7 +340,7 @@ class CI_Security { // 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); + $str = preg_replace('/<\?(php)/i', '<?\\1', $str); } else { @@ -398,7 +360,7 @@ class CI_Security { foreach ($words as $word) { - $word = implode("\s*", str_split($word)) . "\s*"; + $word = implode('\s*', str_split($word)).'\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" @@ -415,19 +377,19 @@ class CI_Security { { $original = $str; - if (preg_match("/<a/i", $str)) + if (preg_match('/<a/i', $str)) { - $str = preg_replace_callback("#<a\s+([^>]*?)(>|$)#si", array($this, '_js_link_removal'), $str); + $str = preg_replace_callback('#<a\s+([^>]*?)(>|$)#si', array($this, '_js_link_removal'), $str); } - if (preg_match("/<img/i", $str)) + if (preg_match('/<img/i', $str)) { - $str = preg_replace_callback("#<img\s+([^>]*?)(\s?/?>|$)#si", array($this, '_js_img_removal'), $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)) + if (preg_match('/(script|xss)/i', $str)) { - $str = preg_replace("#<(/*)(script|xss)(.*?)\>#si", '[removed]', $str); + $str = preg_replace('#<(/*)(script|xss)(.*?)\>#si', '[removed]', $str); } } while($original != $str); @@ -454,15 +416,16 @@ class CI_Security { * * Similar to above, only instead of looking for * tags it looks for PHP and JavaScript commands - * that are disallowed. Rather than removing the + * 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') + * 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); - + $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 @@ -478,13 +441,12 @@ class CI_Security { * 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; + return ($str === $converted_string); } - log_message('debug', "XSS Filtering completed"); + log_message('debug', 'XSS Filtering completed'); return $str; } @@ -516,7 +478,7 @@ class CI_Security { * The reason we are not using html_entity_decode() by itself is because * while it is not technically correct to leave out the semicolon * at the end of an entity most browsers will still interpret the entity - * correctly. html_entity_decode() does not convert entities without + * correctly. html_entity_decode() does not convert entities without * semicolons, so we are left with our own little solution here. Bummer. * * @param string @@ -552,38 +514,23 @@ class CI_Security { public function sanitize_filename($str, $relative_path = FALSE) { $bad = array( - "../", - "<!--", - "-->", - "<", - ">", - "'", - '"', - '&', - '$', - '#', - '{', - '}', - '[', - ']', - '=', - ';', - '?', - "%20", - "%22", - "%3c", // < - "%253c", // < - "%3e", // > - "%0e", // > - "%28", // ( - "%29", // ) - "%2528", // ( - "%26", // & - "%24", // $ - "%3f", // ? - "%3b", // ; - "%3d" // = - ); + '../', '<!--', '-->', '<', '>', + "'", '"', '&', '$', '#', + '{', '}', '[', ']', '=', + ';', '?', '%20', '%22', + '%3c', // < + '%253c', // < + '%3e', // > + '%0e', // > + '%28', // ( + '%29', // ) + '%2528', // ( + '%26', // & + '%24', // $ + '%3f', // ? + '%3b', // ; + '%3d' // = + ); if ( ! $relative_path) { @@ -636,26 +583,26 @@ class CI_Security { if ($is_image === TRUE) { /* - * Adobe Photoshop puts XML metadata into JFIF images, + * 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 without quotes - preg_match_all("/(".implode('|', $evil_attributes).")\s*=\s*([^\s]*)/is", $str, $matches, PREG_SET_ORDER); - + preg_match_all('/('.implode('|', $evil_attributes).')\s*=\s*([^\s]*)/is', $str, $matches, PREG_SET_ORDER); + foreach ($matches as $attr) { $attribs[] = preg_quote($attr[0], '/'); } - + // 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); + preg_match_all('/('.implode('|', $evil_attributes).')\s*=\s*(\042|\047)([^\\2]*?)(\\2)/is', $str, $matches, PREG_SET_ORDER); foreach ($matches as $attr) { @@ -665,11 +612,11 @@ class CI_Security { // 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$5', $str, -1, $count); + $str = preg_replace('/<(\/?[^><]+?)([^A-Za-z\-])('.implode('|', $attribs).')([\s><])([><]*)/i', '<$1$2$4$5', $str, -1, $count); } - + } while ($count); - + return $str; } @@ -685,14 +632,9 @@ class CI_Security { */ 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; + return '<'.$matches[1].$matches[2].$matches[3] // encode opening brace + // encode captured opening or closing brace to prevent recursive vectors: + .str_replace(array('>', '<'), array('>', '<'), $matches[4]); } // -------------------------------------------------------------------- @@ -710,9 +652,12 @@ class CI_Security { */ protected function _js_link_removal($match) { - $attributes = $this->_filter_attributes(str_replace(array('<', '>'), '', $match[1])); - - return str_replace($match[1], preg_replace("#href=.*?(alert\(|alert&\#40;|javascript\:|livescript\:|mocha\:|charset\=|window\.|document\.|\.cookie|<script|<xss|base64\s*,)#si", "", $attributes), $match[0]); + return str_replace($match[1], + preg_replace('#href=.*?(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]); } // -------------------------------------------------------------------- @@ -730,9 +675,12 @@ class CI_Security { */ protected function _js_img_removal($match) { - $attributes = $this->_filter_attributes(str_replace(array('<', '>'), '', $match[1])); - - return str_replace($match[1], preg_replace("#src=.*?(alert\(|alert&\#40;|javascript\:|livescript\:|mocha\:|charset\=|window\.|document\.|\.cookie|<script|<xss|base64\s*,)#si", "", $attributes), $match[0]); + 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]); } // -------------------------------------------------------------------- @@ -763,12 +711,11 @@ class CI_Security { 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); + $out .= preg_replace('#/\*.*?\*/#s', '', $match); } } @@ -806,33 +753,28 @@ class CI_Security { * Protect GET variables in URLs */ - // 901119URL5918AMP18930PROTECT8198 - - $str = preg_replace('|\&([a-z\_0-9\-]+)\=([a-z\_0-9\-]+)|i', $this->xss_hash()."\\1=\\2", $str); + // 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); + $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); + $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; + return str_replace($this->xss_hash(), '&', $str); } // ---------------------------------------------------------------------- @@ -847,14 +789,11 @@ class CI_Security { */ protected function _do_never_allowed($str) { - foreach ($this->_never_allowed_str as $key => $val) - { - $str = str_replace($key, $val, $str); - } + $str = str_replace(array_keys($this->_never_allowed_str), $this->_never_allowed_str, $str); - foreach ($this->_never_allowed_regex as $key => $val) + foreach ($this->_never_allowed_regex as $regex) { - $str = preg_replace("#".$key."#i", $val, $str); + $str = preg_replace('#'.$regex.'#i', '[removed]', $str); } return $str; @@ -891,4 +830,4 @@ class CI_Security { } /* End of file Security.php */ -/* Location: ./system/core/Security.php */
\ No newline at end of file +/* Location: ./system/core/Security.php */ |