diff options
Diffstat (limited to 'system/core/Security.php')
-rw-r--r-- | system/core/Security.php | 86 |
1 files changed, 60 insertions, 26 deletions
diff --git a/system/core/Security.php b/system/core/Security.php index 70cf3e013..faa52d746 100644 --- a/system/core/Security.php +++ b/system/core/Security.php @@ -18,7 +18,7 @@ * * @package CodeIgniter * @author EllisLab Dev Team - * @copyright Copyright (c) 2008 - 2013, EllisLab, Inc. (http://ellislab.com/) + * @copyright Copyright (c) 2008 - 2014, 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 @@ -62,6 +62,19 @@ class CI_Security { ); /** + * HTML5 entities + * + * @var array + */ + public $html5_entities = array( + ':' => ':', + '(' => '(', + ')' => ')', + '&newline;' => "\n", + '&tab;' => "\t" + ); + + /** * XSS Hash * * Random Hash for protecting URLs. @@ -117,7 +130,6 @@ class CI_Security { 'document.write' => '[removed]', '.parentNode' => '[removed]', '.innerHTML' => '[removed]', - 'window.location' => '[removed]', '-moz-binding' => '[removed]', '<!--' => '<!--', '-->' => '-->', @@ -132,9 +144,13 @@ class CI_Security { */ protected $_never_allowed_regex = array( 'javascript\s*:', + '(document|(document\.)?window)\.(location|on\w*)', 'expression\s*(\(|&\#40;)', // CSS and IE 'vbscript\s*:', // IE, surprise! - 'Redirect\s+302', + 'wscript\s*:', // IE + 'jscript\s*:', // IE + 'vbs\s*:', // IE + 'Redirect\s+30\d', "([\"'])?data\s*:[^\\1]*?base64[^\\1]*?,[^\\1]*?\\1?" ); @@ -343,7 +359,11 @@ class CI_Security { * * Note: Use rawurldecode() so it does not remove plus signs */ - $str = rawurldecode($str); + do + { + $str = rawurldecode($str); + } + while (preg_match('/%[0-9a-f]{2,}/i', $str)); /* * Convert character entities to ASCII @@ -402,8 +422,9 @@ class CI_Security { * These words are compacted back to their correct state. */ $words = array( - 'javascript', 'expression', 'vbscript', 'script', 'base64', - 'applet', 'alert', 'document', 'write', 'cookie', 'window' + 'javascript', 'expression', 'vbscript', 'jscript', 'wscript', + 'vbs', 'script', 'base64', 'applet', 'alert', 'document', + 'write', 'cookie', 'window', 'confirm', 'prompt' ); foreach ($words as $word) @@ -420,6 +441,12 @@ class CI_Security { * 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 + * + * Note: It was reported that not only space characters, but all in + * the following pattern can be parsed as separators between a tag name + * and its attributes: [\d\s"\'`;,\/\=\(\x00\x0B\x09\x0C] + * ... however, remove_invisible_characters() above already strips the + * hex-encoded ones, so we'll skip them below. */ do { @@ -427,12 +454,12 @@ class CI_Security { 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\d"\'`;/=,\(\\\\]+([^>]*?)(?:>|$)#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); + $str = preg_replace_callback('#<img[\s\d"\'`;/=,\(\\\\]+([^>]*?)(?:\s?/?>|$)#si', array($this, '_js_img_removal'), $str); } if (preg_match('/script|xss/i', $str)) @@ -456,7 +483,7 @@ class CI_Security { * 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'; + $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); /* @@ -471,7 +498,7 @@ class CI_Security { * 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', + $str = preg_replace('#(alert|prompt|confirm|cmd|passthru|eval|exec|expression|system|fopen|fsockopen|file|file_get_contents|readfile|unlink)(\s*)\((.*?)\)#si', '\\1\\2(\\3)', $str); @@ -551,13 +578,13 @@ class CI_Security { do { - $matches = $matches1 = 0; + $m1 = $m2 = 0; + $str = preg_replace('/(�*[0-9a-f]{2,5})(?![0-9a-f;])/iS', '$1;', $str, -1, $m1); + $str = preg_replace('/(&#\d{2,4})(?![0-9;])/S', '$1;', $str, -1, $m2); $str = html_entity_decode($str, ENT_COMPAT, $charset); - $str = preg_replace('~&#x(0*[0-9a-f]{2,5})~ei', 'chr(hexdec("\\1"))', $str, -1, $matches); - $str = preg_replace('~&#([0-9]{2,4})~e', 'chr(\\1)', $str, -1, $matches1); } - while ($matches OR $matches1); + while ($m1 OR $m2); return $str; } @@ -603,7 +630,7 @@ class CI_Security { */ public function strip_image_tags($str) { - return preg_replace(array('#<img\s+.*?src\s*=\s*["\'](.+?)["\'].*?\>#', '#<img\s+.*?src\s*=\s*(.+?).*?\>#'), '\\1', $str); + return preg_replace(array('#<img[\s/]+.*?src\s*=\s*["\'](.+?)["\'].*?\>#', '#<img[\s/]+.*?src\s*=\s*(.+?).*?\>#'), '\\1', $str); } // ---------------------------------------------------------------- @@ -648,8 +675,7 @@ class CI_Security { */ 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'); + $evil_attributes = array('on\w*', 'style', 'xmlns', 'formaction', 'form', 'xlink:href'); if ($is_image === TRUE) { @@ -665,7 +691,7 @@ class CI_Security { $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); + preg_match_all('/(?<!\w)('.implode('|', $evil_attributes).')\s*=\s*(\042|\047)([^\\2]*?)(\\2)/is', $str, $matches, PREG_SET_ORDER); foreach ($matches as $attr) { @@ -673,7 +699,7 @@ class CI_Security { } // 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('/(?<!\w)('.implode('|', $evil_attributes).')\s*=\s*([^\s>]*)/is', $str, $matches, PREG_SET_ORDER); foreach ($matches as $attr) { @@ -727,7 +753,7 @@ class CI_Security { 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', + 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])) ), @@ -752,7 +778,7 @@ class CI_Security { 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', + 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])) ), @@ -810,7 +836,14 @@ class CI_Security { */ protected function _decode_entity($match) { - return $this->entity_decode($match[0], strtoupper(config_item('charset'))); + // entity_decode() won't convert dangerous HTML5 entities + // (it could, but ENT_HTML5 is only available since PHP 5.4), + // so we'll do that here + return str_ireplace( + array_keys($this->html5_entities), + array_values($this->html5_entities), + $this->entity_decode($match[0], strtoupper(config_item('charset'))) + ); } // -------------------------------------------------------------------- @@ -837,14 +870,15 @@ class CI_Security { * 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('/(&#\d{2,4})(?![0-9;])/', '$1;', $str); + $str = preg_replace('/(&[a-z]{2,})(?![a-z;])/i', '$1;', $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('/(�*[0-9a-f]{2,5})(?![0-9a-f;])/i', '$1;', $str); /* * Un-Protect GET variables in URLs @@ -884,7 +918,7 @@ class CI_Security { { if ($this->_csrf_hash === '') { - // If the cookie exists we will use it's value. + // If the cookie exists we will use its value. // We don't necessarily want to regenerate it with // each page load since a page could contain embedded // sub-pages causing this feature to fail @@ -894,7 +928,7 @@ class CI_Security { return $this->_csrf_hash = $_COOKIE[$this->_csrf_cookie_name]; } - $this->_csrf_hash = md5(uniqid(rand(), TRUE)); + $this->_csrf_hash = md5(uniqid(mt_rand(), TRUE)); $this->csrf_set_cookie(); } |