$value) { if (in_array($key, $exception)) $ar[$key] = $value; else { if(is_array($value)) $ar[$key] = $this->html_clean($value); else { $value = htmlspecialchars_decode($value); $ar[$key] = htmlspecialchars($value, ENT_NOQUOTES); } } } return $ar; } } protected function _remove_evil_attributes($str, $is_image) { // All javascript event handlers (e.g. onload, onclick, onmouseover), style, and xmlns $evil_attributes = array('[^acti]on\w*', '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 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], '/'); } // 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], '/'); } // 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 $3$5$6$7', $str, -1, $count); } } while ($count); return $str; } 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', '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("/]*?)(>|$)#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|frameset|frame|head|html|ilayer|iframe|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; } }