The CI security class provides a global defense against CSRF attacks and XSS attack policies that require only the configuration file to be opened:
Copy Code code as follows:
$config [' csrf_protection '] = TRUE;
$config [' global_xss_filtering '] = TRUE;
and provides a practical method:
Copy Code code as follows:
$this->security->xss_clean ($data);//The second argument is true to verify that the picture is secure
$this->security->sanitize_filename ()//filter file name
CI also provides a security function:
Xss_clean ()//XSS Filtration
Sanitize_filename ()//clean file name
Do_hash ()//md5 or SHA encryption
Strip_image_tags ()//remove unnecessary characters from picture labels
Encode_php_tags ()//force PHP script tag into entity object
Copy Code code as follows:
<?php if (! defined (' BasePath ')) exit (' No Direct script access allowed ');
/**
* Safety Class
*/
Class Ci_security {
Random hash value of URL
protected $_xss_hash = ';
Hash value of the cookie tag against the CSRF attack
protected $_csrf_hash = ';
Anti-CSRF cookie expiration Time
protected $_csrf_expire = 7200;
Anti-CSRF cookie name
protected $_csrf_token_name = ' Ci_csrf_token ';
Anti-CSRF Token name
protected $_csrf_cookie_name = ' Ci_csrf_token ';
Array of strings not allowed to appear
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> '
);
Array of regular expressions that are not allowed to appear
Protected $_never_allowed_regex = Array (
' javascript\s*: ',
' Expression\s* (|&\ #40;) ',//CSS and IE
' vbscript\s*: ',//IE, surprise!
' redirect\s+302 ',
"([\"]) data\s*:[^\\1]*?base64[^\\1]*?,[^\\1]*?\\1? "
);
Constructors
Public Function __construct ()
{
CSRF protection is turned on
if (Config_item (' csrf_protection ') = = TRUE)
{
CSRF Configuration
foreach (Array (' Csrf_expire ', ' csrf_token_name ', ' csrf_cookie_name ') as $key)
{
if (FALSE!== ($val = Config_item ($key)))
{
$this->{' _ '. $key} = $val;
}
}
_csrf_cookie_name Plus Cookie Prefix
if (Config_item (' Cookie_prefix '))
{
$this->_csrf_cookie_name = Config_item (' Cookie_prefix '). $this->_csrf_cookie_name;
}
Set the hash value of the CSRF
$this->_csrf_set_hash ();
}
Log_message (' Debug ', "Security Class initialized");
}
// --------------------------------------------------------------------
/**
* Verify Cross Site Request forgery Protection
*
* @return Object
*/
Public Function csrf_verify ()
{
If it is not a POST request, set the CSRF cookie value
if (Strtoupper ($_server[' Request_method '))!== ' POST ')
{
return $this->csrf_set_cookie ();
}
Does 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 match?
if ($_post[$this->_csrf_token_name]!= $_cookie[$this->_csrf_cookie_name])
{
$this->csrf_show_error ();
}
We kill this since we do 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 the CSRF cookie value
*/
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 Save
Public Function Csrf_show_error ()
{
Show_error (' The action you have requested are not allowed. ');
}
Gets the hash value of the CSRF
Public Function Get_csrf_hash ()
{
return $this->_csrf_hash;
}
Gets the token value of the CSRF
Public Function Get_csrf_token_name ()
{
return $this->_csrf_token_name;
}
/**
* XSS Filtering
*/
Public Function Xss_clean ($str, $is _image = FALSE)
{
Whether it is an array
if (Is_array ($STR))
{
while (the list ($key) = each ($STR))
{
$STR [$key] = $this->xss_clean ($str [$key]);
}
return $str;
}
Remove the visible string
$str = Remove_invisible_characters ($STR);
Validating entity URLs
$str = $this->_validate_entities ($STR);
/*
* URL decoding
*
* Just in case stuff like this is submitted:
*
* <a href= "GoogleHttp://%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 we tests below to work reliably.
* We convert entities that are within tags since
* These are the ones that would 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 is found to is amazingly slow here
* 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:
*
* <?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
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 ',
' Applets ', ' 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 are followed by a non-word character the It is want.
That's 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 does some version comparisons and use the Stripos for PHP5,
* But it is dog slow compared to this simplified non-capturing
* Preg_match (), especially if the pattern exists in the string
*/
Todo
{
$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) 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 the 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 this after all of the character
* Conversion is do whether any unwanted, likely XSS, the code was found.
* If not, we return TRUE and 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;
}
// --------------------------------------------------------------------
A random hash value that protects a URL
Public Function Xss_hash ()
{
if ($this->_xss_hash = = ")
{
Mt_srand ();
$this->_xss_hash = MD5 (time () + mt_rand (0, 1999999999));
}
return $this->_xss_hash;
}
// --------------------------------------------------------------------
/**
* HTML Entity Transfer code
*/
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 (' ~& #x (0*[0-9a-f]{2,5}) ~ei ', ' Chr (Hexdec ("\\1")) ', $str);
Return Preg_replace (' ~&# ([0-9]{2,4}) ~e ', ' Chr (\\1) ', $str);
}
// --------------------------------------------------------------------
Filter filename to ensure file name security
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));
}
Compress words like J A V a s C r I p t into JavaScript
protected function _compact_exploded_words ($matches)
{
Return preg_replace ('/\s+/s ', ', $matches [1]). $matches [2];
}
// --------------------------------------------------------------------
/*
* Remove some of the harmful HTML attributes
*/
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 ', ' 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 attributes strings with quotes (042 and 047 are octal)
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], '/');
}
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;
}
// --------------------------------------------------------------------
/**
* Clean HTML, to complement the closed label
*/
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
$str. = str_replace (Array (' > ', ' < '), array (' > ', ' < '),
$matches [4]);
return $str;
}
// --------------------------------------------------------------------
/**
* Filter Hyperlinks in JS
*/
protected function _js_link_removal ($match)
{
Return Str_replace (
$match [1],
Preg_replace (
' #href =.*? (Alert\ (|alert&\ #40; |javascript\:|livescript\:|mocha\:|charset\=|window\.| document\.| \.cookie|<script|<xss|data\s*:) #si ',
'',
$this->_filter_attributes (Array (' < ', ' > '), ', $match [1])
),
$match [0]
);
}
// --------------------------------------------------------------------
/**
* Filter the JS in the picture link
*/
protected function _js_img_removal ($match)
{
Return Str_replace (
$match [1],
Preg_replace (
' #src =.*? (Alert\ (|alert&\ #40; |javascript\:|livescript\:|mocha\:|charset\=|window\.| document\.| \.cookie|<script|<xss|base64\s*,) #si ',
'',
$this->_filter_attributes (Array (' < ', ' > '), ', $match [1])
),
$match [0]
);
}
// --------------------------------------------------------------------
/**
* Convert attributes to convert some characters to entities
*/
protected function _convert_attribute ($match)
{
return Str_replace (' > ', ' < ', ' \ \ \ '), Array (' > ', ' < ', ' \\\\ '), $match [0]);
}
// --------------------------------------------------------------------
Filter HTML Tag Properties
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 Transfer code
protected function _decode_entity ($match)
{
return $this->entity_decode ($match [0], Strtoupper (Config_item (' CharSet '));
}
// --------------------------------------------------------------------
/**
* Verify URL Entity
*/
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 the 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;
}
// ----------------------------------------------------------------------
Filter for strings that are not allowed to appear
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;
}
// --------------------------------------------------------------------
Set the hash value of the CSRF
protected function _csrf_set_hash ()
{
if ($this->_csrf_hash = = ")
{
If _csrf_cookie_name exists, direct as CSRF hash value
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];
}
Otherwise random a MD5 string
return $this->_csrf_hash = MD5 (Uniqid (rand (), TRUE);
}
return $this->_csrf_hash;
}
}