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;
}
}