CI框架安全类Security.php源码分析
CI安全类提供了全局防御CSRF攻击和XSS攻击策略,只需要在配置文件开启即可:
$config['csrf_protection']=TRUE; $config['global_xss_filtering']=TRUE;
并提供了实用方法:
$this->security->xss_clean($data);//第二个参数为TRUE,验证图片安全 $this->security->sanitize_filename()//过滤文件名
CI也提供了安全函数:
xss_clean()//xss过滤
sanitize_filename()//净化文件名
do_hash()//md5或sha加密
strip_image_tags()//删除图片标签的不必要字符
encode_php_tags()//把PHP脚本标签强制转成实体对象
<?phpif(!defined('BASEPATH'))exit('Nodirectscriptaccessallowed'); /** *安全类 */ classCI_Security{ //url的随机hash值 protected$_xss_hash =''; //防csrf攻击的cookie标记的哈希值 protected$_csrf_hash =''; //防csrfcookie过期时间 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( 'javascript\s*:', 'expression\s*(\(|&\#40;)',//CSSandIE 'vbscript\s*:',//IE,surprise! 'Redirect\s+302', "([\"'])?data\s*:[^\\1]*?base64[^\\1]*?,[^\\1]*?\\1?" ); //构造函数 publicfunction__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',"SecurityClassInitialized"); } //-------------------------------------------------------------------- /** *VerifyCrossSiteRequestForgeryProtection * *@return object */ publicfunctioncsrf_verify() { //如果不是post请求,则设置csrf的cookie值 if(strtoupper($_SERVER['REQUEST_METHOD'])!=='POST') { return$this->csrf_set_cookie(); } //Dothetokensexistinboththe_POSTand_COOKIEarrays? 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(); } //Wekillthissincewe'redoneandwedon'twantto //polutethe_POSTarray unset($_POST[$this->_csrf_token_name]); //Nothingshouldlastforever unset($_COOKIE[$this->_csrf_cookie_name]); $this->_csrf_set_hash(); $this->csrf_set_cookie(); log_message('debug','CSRFtokenverified'); return$this; } //-------------------------------------------------------------------- /** *设置csrf的cookie值 */ publicfunctioncsrf_set_cookie() { $expire=time()+$this->_csrf_expire; $secure_cookie=(config_item('cookie_secure')===TRUE)?1:0; if($secure_cookie&&(empty($_SERVER['HTTPS'])ORstrtolower($_SERVER['HTTPS'])==='off')) { returnFALSE; } setcookie($this->_csrf_cookie_name,$this->_csrf_hash,$expire,config_item('cookie_path'),config_item('cookie_domain'),$secure_cookie); log_message('debug',"CRSFcookieSet"); return$this; } //csrf保存 publicfunctioncsrf_show_error() { show_error('Theactionyouhaverequestedisnotallowed.'); } //获取csrf的hash值 publicfunctionget_csrf_hash() { return$this->_csrf_hash; } //获取csrf的token值 publicfunctionget_csrf_token_name() { return$this->_csrf_token_name; } /** *XSS过滤 */ publicfunctionxss_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解码 * *Justincasestufflikethisissubmitted: * *<ahref="http://%77%77%77%2E%67%6F%6F%67%6C%65%2E%63%6F%6D">Google</a> * *Note:Userawurldecode()soitdoesnotremoveplussigns * */ $str=rawurldecode($str); /* *ConvertcharacterentitiestoASCII * *Thispermitsourtestsbelowtoworkreliably. *Weonlyconvertentitiesthatarewithintagssince *thesearetheonesthatwillposesecurityproblems. * */ $str=preg_replace_callback("/[a-z]+=([\'\"]).*?\\1/si",array($this,'_convert_attribute'),$str); $str=preg_replace_callback("/<\w+.*?(?=>|<|$)/si",array($this,'_decode_entity'),$str); /* *RemoveInvisibleCharactersAgain! */ $str=remove_invisible_characters($str); /* *Convertalltabstospaces * *Thispreventsstringslikethis:ja vascript *NOTE:wedealwithspacesbetweencharacterslater. *NOTE:preg_replacewasfoundtobeamazinglyslowhereon *largeblocksofdata,soweusestr_replace. */ if(strpos($str,"\t")!==FALSE) { $str=str_replace("\t",'',$str); } /* *Captureconvertedstringforlatercomparison */ $converted_string=$str; //RemoveStringsthatareneverallowed $str=$this->_do_never_allowed($str); /* *MakesPHPtagssafe * *Note:XMLtagsareinadvertentlyreplacedtoo: * *<?xml * *Butitdoesn'tseemtoposeaproblem. */ if($is_image===TRUE) { //ImageshaveatendencytohavethePHPshortopeningand //closingtagseverysooftensoweskipthoseandonly //dothelongopeningtags. $str=preg_replace('/<\?(php)/i',"<?\\1",$str); } else { $str=str_replace(array('<?','?'.'>'), array('<?','?>'),$str); } /* *Compactanyexplodedwords * *Thiscorrectswordslike: javascript *Thesewordsarecompactedbacktotheircorrectstate. */ $words=array( 'javascript','expression','vbscript','script','base64', 'applet','alert','document','write','cookie','window' ); foreach($wordsas$word) { $temp=''; for($i=0,$wordlen=strlen($word);$i<$wordlen;$i++) { $temp.=substr($word,$i,1)."\s*"; } //Weonlywanttodothiswhenitisfollowedbyanon-wordcharacter //Thatwayvalidstufflike"dealerto"doesnotbecome"dealerto" $str=preg_replace_callback('#('.substr($temp,0,-3).')(\W)#is',array($this,'_compact_exploded_words'),$str); } /* *RemovedisallowedJavascriptinlinksorimgtags *WeusedtodosomeversioncomparisonsanduseofstriposforPHP5, *butitisdogslowcomparedtothesesimplifiednon-capturing *preg_match(),especiallyifthepatternexistsinthestring */ do { $original=$str; if(preg_match("/<a/i",$str)) { $str=preg_replace_callback("#<a\s+([^>]*?)(>|$)#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); } if(preg_match("/script/i",$str)ORpreg_match("/xss/i",$str)) { $str=preg_replace("#<(/*)(script|xss)(.*?)\>#si",'[removed]',$str); } } while($original!=$str); unset($original); //Removeevilattributessuchasstyle,onclickandxmlns $str=$this->_remove_evil_attributes($str,$is_image); /* *SanitizenaughtyHTMLelements * *Ifatagcontaininganyofthewordsinthelist *belowisfound,thetaggetsconvertedtoentities. * *Sothis:<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); /* *Sanitizenaughtyscriptingelements * *Similartoabove,onlyinsteadoflookingfor *tagsitlooksforPHPandJavaScriptcommands *thataredisallowed. Ratherthanremovingthe *code,itsimplyconvertstheparenthesistoentities *renderingthecodeun-executable. * *Forexample: eval('somecode') *Becomes: eval('somecode') */ $str=preg_replace('#(alert|cmd|passthru|eval|exec|expression|system|fopen|fsockopen|file|file_get_contents|readfile|unlink)(\s*)\((.*?)\)#si',"\\1\\2(\\3)",$str); //Finalcleanup //Thisaddsabitofextraprecautionincase //somethinggotthroughtheabovefilters $str=$this->_do_never_allowed($str); /* *ImagesareHandledinaSpecialWay *-Essentially,wewanttoknowthatafterallofthecharacter *conversionisdonewhetheranyunwanted,likelyXSS,codewasfound. *Ifnot,wereturnTRUE,astheimageisclean. *However,ifthestringpost-conversiondoesnotmatchedthe *stringpost-removalofXSS,thenitfails,astherewasunwantedXSS *codefoundandremoved/changedduringprocessing. */ if($is_image===TRUE) { return($str==$converted_string)?TRUE:FALSE; } log_message('debug',"XSSFilteringcompleted"); return$str; } //-------------------------------------------------------------------- //保护url的随机hash值 publicfunctionxss_hash() { if($this->_xss_hash=='') { mt_srand(); $this->_xss_hash=md5(time()+mt_rand(0,1999999999)); } return$this->_xss_hash; } //-------------------------------------------------------------------- /** *html实体转码 */ publicfunctionentity_decode($str,$charset='UTF-8') { if(stristr($str,'&')===FALSE) { return$str; } $str=html_entity_decode($str,ENT_COMPAT,$charset); $str=preg_replace('~&#x(0*[0-9a-f]{2,5})~ei','chr(hexdec("\\1"))',$str); returnpreg_replace('~&#([0-9]{2,4})~e','chr(\\1)',$str); } //-------------------------------------------------------------------- //过滤文件名,保证文件名安全 publicfunctionsanitize_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); returnstripslashes(str_replace($bad,'',$str)); } //压缩单词如javascript成javascript protectedfunction_compact_exploded_words($matches) { returnpreg_replace('/\s+/s','',$matches[1]).$matches[2]; } //-------------------------------------------------------------------- /* *去掉一些危害的html属性 */ protectedfunction_remove_evil_attributes($str,$is_image) { //Alljavascripteventhandlers(e.g.onload,onclick,onmouseover),style,andxmlns $evil_attributes=array('on\w*','style','xmlns','formaction'); if($is_image===TRUE) { /* *AdobePhotoshopputsXMLmetadataintoJFIFimages, *includingnamespacing,sowehavetoallowthisforimages. */ unset($evil_attributes[array_search('xmlns',$evil_attributes)]); } do{ $count=0; $attribs=array(); //findoccurrencesofillegalattributestringswithquotes(042and047areoctalquotes) preg_match_all('/('.implode('|',$evil_attributes).')\s*=\s*(\042|\047)([^\\2]*?)(\\2)/is',$str,$matches,PREG_SET_ORDER); foreach($matchesas$attr) { $attribs[]=preg_quote($attr[0],'/'); } //findoccurrencesofillegalattributestringswithoutquotes preg_match_all('/('.implode('|',$evil_attributes).')\s*=\s*([^\s>]*)/is',$str,$matches,PREG_SET_ORDER); foreach($matchesas$attr) { $attribs[]=preg_quote($attr[0],'/'); } //replaceillegalattributestringsthatareinsideanhtmltag 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,补齐未关闭的标签 */ protectedfunction_sanitize_naughty_html($matches) { //encodeopeningbrace $str='<'.$matches[1].$matches[2].$matches[3]; //encodecapturedopeningorclosingbracetopreventrecursivevectors $str.=str_replace(array('>','<'),array('>','<'), $matches[4]); return$str; } //-------------------------------------------------------------------- /** *过滤超链接中js */ protectedfunction_js_link_removal($match) { returnstr_replace( $match[1], preg_replace( '#href=.*?(alert\(|alert&\#40;|javascript\:|livescript\:|mocha\:|charset\=|window\.|document\.|\.cookie|<script|<xss|data\s*:)#si', '', $this->_filter_attributes(str_replace(array('<','>'),'',$match[1])) ), $match[0] ); } //-------------------------------------------------------------------- /** *过滤图片链接中的js */ protectedfunction_js_img_removal($match) { returnstr_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] ); } //-------------------------------------------------------------------- /** *转换属性,将一些字符转换成实体 */ protectedfunction_convert_attribute($match) { returnstr_replace(array('>','<','\\'),array('>','<','\\\\'),$match[0]); } //-------------------------------------------------------------------- //过滤html标签属性 protectedfunction_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实体转码 protectedfunction_decode_entity($match) { return$this->entity_decode($match[0],strtoupper(config_item('charset'))); } //-------------------------------------------------------------------- /** *验证url实体 */ protectedfunction_validate_entities($str) { /* *ProtectGETvariablesinURLs */ //901119URL5918AMP18930PROTECT8198 $str=preg_replace('|\&([a-z\_0-9\-]+)\=([a-z\_0-9\-]+)|i',$this->xss_hash()."\\1=\\2",$str); /* *Validatestandardcharacterentities * *Addasemicolonifmissing. Wedothistoenable *theconversionofentitiestoASCIIlater. * */ $str=preg_replace('#(&\#?[0-9a-z]{2,})([\x00-\x20])*;?#i',"\\1;\\2",$str); /* *ValidateUTF16twobyteencoding(x00) * *Justasabove,addsasemicolonifmissing. * */ $str=preg_replace('#(&\#x?)([0-9A-F]+);?#i',"\\1\\2;",$str); /* *Un-ProtectGETvariablesinURLs */ $str=str_replace($this->xss_hash(),'&',$str); return$str; } //---------------------------------------------------------------------- //过滤不允许出现的字符串 protectedfunction_do_never_allowed($str) { $str=str_replace(array_keys($this->_never_allowed_str),$this->_never_allowed_str,$str); foreach($this->_never_allowed_regexas$regex) { $str=preg_replace('#'.$regex.'#is','[removed]',$str); } return$str; } //-------------------------------------------------------------------- //设置csrf的hash值 protectedfunction_csrf_set_hash() { if($this->_csrf_hash=='') { //如果_csrf_cookie_name存在,直接作为csrfhash值 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; } }