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( "javascripts*:", "expressions*((|()", // CSS and IE "vbscripts*:", // IE, surprise! "Redirects+302", "([""])?datas*:[^\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, " ") !== FALSE) { $str = str_replace(" ", " ", $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("#<as+([^>]*?)(>|$)#si", array($this, "_js_link_removal"), $str); } if (preg_match("/<img/i", $str)) { $str = preg_replace_callback("#<imgs+([^>]*?)(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("~(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("onw*", "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*( 42| 47)([^\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(|javascript:|livescript:|mocha:|charset=|window.|document.|.cookie|<script|<xss|datas*:)#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(|javascript:|livescript:|mocha:|charset=|window.|document.|.cookie|<script|<xss|base64s*,)#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*( 42| 47)([^\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("#(?)([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; } }