From 14a0ac63a9dfb72e4681c37f7727cd48882152bd Mon Sep 17 00:00:00 2001 From: Pascal Kriete Date: Tue, 5 Apr 2011 14:55:56 -0400 Subject: Moving security to core. --- system/libraries/Security.php | 820 ------------------------------------------ 1 file changed, 820 deletions(-) delete mode 100644 system/libraries/Security.php (limited to 'system/libraries') diff --git a/system/libraries/Security.php b/system/libraries/Security.php deleted file mode 100644 index ceef9779c..000000000 --- a/system/libraries/Security.php +++ /dev/null @@ -1,820 +0,0 @@ - '[removed]', - 'document.write' => '[removed]', - '.parentNode' => '[removed]', - '.innerHTML' => '[removed]', - 'window.location' => '[removed]', - '-moz-binding' => '[removed]', - '' => '-->', - ' '<![CDATA[' - ); - - /* never allowed, regex replacement */ - protected $_never_allowed_regex = array( - "javascript\s*:" => '[removed]', - "expression\s*(\(|&\#40;)" => '[removed]', // CSS and IE - "vbscript\s*:" => '[removed]', // IE, surprise! - "Redirect\s+302" => '[removed]' - ); - - /** - * Constructor - */ - public function __construct() - { - // Append application specific cookie prefix to token name - $this->_csrf_cookie_name = (config_item('cookie_prefix')) ? config_item('cookie_prefix').$this->_csrf_token_name : $this->_csrf_token_name; - - // Set the CSRF hash - $this->_csrf_set_hash(); - - log_message('debug', "Security Class Initialized"); - } - - // -------------------------------------------------------------------- - - /** - * Verify Cross Site Request Forgery Protection - * - * @return object - */ - public function csrf_verify() - { - // If no POST data exists we will set the CSRF cookie - if (count($_POST) == 0) - { - return $this->csrf_set_cookie(); - } - - // 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])) - { - $this->csrf_show_error(); - } - - // Do the tokens match? - 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; - } - - // -------------------------------------------------------------------- - - /** - * Set Cross Site Request Forgery Protection Cookie - * - * @return object - */ - public function csrf_set_cookie() - { - $expire = time() + $this->_csrf_expire; - $secure_cookie = (config_item('cookie_secure') === TRUE) ? 1 : 0; - - if ($secure_cookie) - { - $req = isset($_SERVER['HTTPS']) ? $_SERVER['HTTPS'] : FALSE; - - if ( ! $req OR $req == '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; - } - - // -------------------------------------------------------------------- - - /** - * Show CSRF Error - * - * @return void - */ - public function csrf_show_error() - { - show_error('The action you have requested is not allowed.'); - } - - // -------------------------------------------------------------------- - - /** - * Get CSRF Hash - * - * Getter Method - * - * @return string self::_csrf_hash - */ - public function get_csrf_hash() - { - return $this->_csrf_hash; - } - - // -------------------------------------------------------------------- - - /** - * Get CSRF Token Name - * - * Getter Method - * - * @return string self::csrf_token_name - */ - public function get_csrf_token_name() - { - return $this->_csrf_token_name; - } - - // -------------------------------------------------------------------- - - /** - * XSS Clean - * - * Sanitizes data so that Cross Site Scripting Hacks can be - * prevented. This function does a fair amount of work but - * it is extremely thorough, designed to prevent even the - * most obscure XSS attempts. Nothing is ever 100% foolproof, - * of course, but I haven't been able to get anything passed - * the filter. - * - * Note: This function should only be used to deal with data - * 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 - * got from Bitflux: http://channel.bitflux.ch/wiki/XSS_Prevention - * - * To help develop this script I used this great list of - * vulnerabilities along with a few other hacks I've - * harvested from examining vulnerabilities in other programs: - * http://ha.ckers.org/xss.html - * - * @param mixed string or array - * @return string - */ - public function xss_clean($str, $is_image = FALSE) - { - /* - * Is the string an array? - * - */ - if (is_array($str)) - { - while (list($key) = each($str)) - { - $str[$key] = $this->xss_clean($str[$key]); - } - - return $str; - } - - /* - * Remove Invisible Characters - */ - $str = remove_invisible_characters($str); - - // Validate Entities in URLs - $str = $this->_validate_entities($str); - - /* - * URL Decode - * - * Just in case stuff like this is submitted: - * - * Google - * - * 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: - * - * '), 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', - '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("/]*?)(>|$)#si", array($this, '_js_link_removal'), $str); - } - - if (preg_match("/]*?)(\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: - * 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; - } - - // -------------------------------------------------------------------- - - /** - * Random Hash for protecting URLs - * - * @return string - */ - public function xss_hash() - { - if ($this->_xss_hash == '') - { - if (phpversion() >= 4.2) - { - mt_srand(); - } - else - { - mt_srand(hexdec(substr(md5(microtime()), -8)) & 0x7fffffff); - } - - $this->_xss_hash = md5(time() + mt_rand(0, 1999999999)); - } - - return $this->_xss_hash; - } - - // -------------------------------------------------------------------- - - /** - * HTML Entities Decode - * - * This function is a replacement for html_entity_decode() - * - * In some versions of PHP the native function does not work - * when UTF-8 is the specified character set, so this gives us - * a work-around. More info here: - * http://bugs.php.net/bug.php?id=25670 - * - * NOTE: html_entity_decode() has a bug in some PHP versions when UTF-8 is the - * character set, and the PHP developers said they were not back porting the - * fix to versions other than PHP 5.x. - * - * @param string - * @param string - * @return string - */ - public function entity_decode($str, $charset='UTF-8') - { - if (stristr($str, '&') === FALSE) return $str; - - // 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 - // semicolons, so we are left with our own little solution here. Bummer. - - if (function_exists('html_entity_decode') && - (strtolower($charset) != 'utf-8')) - { - $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); - } - - // Numeric Entities - $str = preg_replace('~&#x(0*[0-9a-f]{2,5});{0,1}~ei', 'chr(hexdec("\\1"))', $str); - $str = preg_replace('~&#([0-9]{2,4});{0,1}~e', 'chr(\\1)', $str); - - // Literal Entities - Slightly slow so we do another check - if (stristr($str, '&') === FALSE) - { - $str = strtr($str, array_flip(get_html_translation_table(HTML_ENTITIES))); - } - - return $str; - } - - // -------------------------------------------------------------------- - - /** - * Filename Security - * - * @param string - * @return string - */ - 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)); - } - - // ---------------------------------------------------------------- - - /** - * Compact Exploded Words - * - * Callback function for xss_clean() to remove whitespace from - * things like j a v a s c r i p t - * - * @param type - * @return type - */ - protected function _compact_exploded_words($matches) - { - return preg_replace('/\s+/s', '', $matches[1]).$matches[2]; - } - - // -------------------------------------------------------------------- - - /* - * Remove Evil HTML Attributes (like evenhandlers and style) - * - * It removes the evil attribute and either: - * - Everything up until a space - * For example, everything between the pipes: - * - * - Everything inside the quotes - * For example, everything between the pipes: - * - * - * @param string $str The string to check - * @param boolean $is_image TRUE if this is an image - * @return string The string with the evil attributes removed - */ - 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'); - - 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 { - $str = preg_replace( - "#<(/?[^><]+?)([^A-Za-z\-])(".implode('|', $evil_attributes).")(\s*=\s*)([\"][^>]*?[\"]|[\'][^>]*?[\']|[^>]*?)([\s><])([><]*)#i", - "<$1$6", - $str, -1, $count - ); - } while ($count); - - return $str; - } - - // -------------------------------------------------------------------- - - /** - * Sanitize Naughty HTML - * - * Callback function for xss_clean() to remove naughty HTML elements - * - * @param array - * @return string - */ - 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 Link Removal - * - * Callback function for xss_clean() to sanitize links - * This limits the PCRE backtracks, making it more performance friendly - * and prevents PREG_BACKTRACK_LIMIT_ERROR from being triggered in - * PHP 5.2+ on link-heavy strings - * - * @param array - * @return string - */ - 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|_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|', '<', '\\'), array('>', '<', '\\\\'), $match[0]); - } - - // -------------------------------------------------------------------- - - /** - * Filter Attributes - * - * Filters tag attributes for consistency and safety - * - * @param string - * @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 Entity Decode Callback - * - * Used as a callback for XSS Clean - * - * @param array - * @return string - */ - protected function _decode_entity($match) - { - return $this->entity_decode($match[0], strtoupper(config_item('charset'))); - } - - // -------------------------------------------------------------------- - - /** - * Validate URL entities - * - * Called by xss_clean() - * - * @param string - * @return string - */ - 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; - } - - // ---------------------------------------------------------------------- - - /** - * Do Never Allowed - * - * A utility function for xss_clean() - * - * @param string - * @return string - */ - protected function _do_never_allowed($str) - { - foreach ($this->_never_allowed_str as $key => $val) - { - $str = str_replace($key, $val, $str); - } - - foreach ($this->_never_allowed_regex as $key => $val) - { - $str = preg_replace("#".$key."#i", $val, $str); - } - - return $str; - } - - // -------------------------------------------------------------------- - - /** - * Set Cross Site Request Forgery Protection Cookie - * - * @return string - */ - protected function _csrf_set_hash() - { - if ($this->_csrf_hash == '') - { - // If the cookie exists we will use it's 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 - if (isset($_COOKIE[$this->_csrf_cookie_name]) && - $_COOKIE[$this->_csrf_cookie_name] != '') - { - return $this->_csrf_hash = $_COOKIE[$this->_csrf_cookie_name]; - } - - return $this->_csrf_hash = md5(uniqid(rand(), TRUE)); - } - - return $this->_csrf_hash; - } - -} -// END Security Class - -/* End of file Security.php */ -/* Location: ./system/libraries/Security.php */ \ No newline at end of file -- cgit v1.2.3-24-g4f1b