blob: 2db8ee9b3555ff4bb6909b861f3526e0e1df4f5e [file] [log] [blame]
Derek Jonese701d762010-03-02 18:17:01 -06001<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
2/**
3 * CodeIgniter
4 *
5 * An open source application development framework for PHP 4.3.2 or newer
6 *
7 * @package CodeIgniter
8 * @author ExpressionEngine Dev Team
9 * @copyright Copyright (c) 2008 - 2010, EllisLab, Inc.
10 * @license http://codeigniter.com/user_guide/license.html
11 * @link http://codeigniter.com
12 * @since Version 1.0
13 * @filesource
14 */
15
16// ------------------------------------------------------------------------
17
18/**
19 * Security Class
20 *
21 * @package CodeIgniter
22 * @subpackage Libraries
23 * @category Security
24 * @author ExpressionEngine Dev Team
25 * @link http://codeigniter.com/user_guide/libraries/sessions.html
26 */
27class CI_Security {
Barry Mienydd671972010-10-04 16:33:58 +020028 var $xss_hash = '';
29 var $csrf_hash = '';
Derek Jonese701d762010-03-02 18:17:01 -060030 var $csrf_expire = 7200; // Two hours (in seconds)
31 var $csrf_token_name = 'ci_csrf_token';
Derek Jones95b183ad2010-08-31 09:42:39 -050032 var $csrf_cookie_name = 'ci_csrf_token';
Barry Mienydd671972010-10-04 16:33:58 +020033
Derek Jonese701d762010-03-02 18:17:01 -060034 /* never allowed, string replacement */
35 var $never_allowed_str = array(
36 'document.cookie' => '[removed]',
37 'document.write' => '[removed]',
38 '.parentNode' => '[removed]',
39 '.innerHTML' => '[removed]',
40 'window.location' => '[removed]',
41 '-moz-binding' => '[removed]',
42 '<!--' => '&lt;!--',
43 '-->' => '--&gt;',
44 '<![CDATA[' => '&lt;![CDATA['
45 );
46 /* never allowed, regex replacement */
47 var $never_allowed_regex = array(
48 "javascript\s*:" => '[removed]',
49 "expression\s*(\(|&\#40;)" => '[removed]', // CSS and IE
50 "vbscript\s*:" => '[removed]', // IE, surprise!
51 "Redirect\s+302" => '[removed]'
52 );
53
54 function CI_Security()
55 {
Derek Jonesb3f10a22010-07-25 19:11:26 -050056 // Append application specific cookie prefix to token name
Derek Jones95b183ad2010-08-31 09:42:39 -050057 $this->csrf_cookie_name = (config_item('cookie_prefix')) ? config_item('cookie_prefix').$this->csrf_token_name : $this->csrf_token_name;
Derek Jonesb3f10a22010-07-25 19:11:26 -050058
Derek Jonese701d762010-03-02 18:17:01 -060059 // Set the CSRF hash
60 $this->_csrf_set_hash();
Derek Allard958543a2010-07-22 14:10:26 -040061
Derek Jonese701d762010-03-02 18:17:01 -060062 log_message('debug', "Security Class Initialized");
63 }
64
65 // --------------------------------------------------------------------
Barry Mienydd671972010-10-04 16:33:58 +020066
Derek Jonese701d762010-03-02 18:17:01 -060067 /**
68 * Verify Cross Site Request Forgery Protection
69 *
70 * @access public
71 * @return null
72 */
73 function csrf_verify()
Derek Allard958543a2010-07-22 14:10:26 -040074 {
Derek Jonese701d762010-03-02 18:17:01 -060075 // If no POST data exists we will set the CSRF cookie
76 if (count($_POST) == 0)
77 {
78 return $this->csrf_set_cookie();
79 }
80
81 // Do the tokens exist in both the _POST and _COOKIE arrays?
Derek Jones95b183ad2010-08-31 09:42:39 -050082 if ( ! isset($_POST[$this->csrf_token_name]) OR ! isset($_COOKIE[$this->csrf_cookie_name]))
Derek Jonese701d762010-03-02 18:17:01 -060083 {
84 $this->csrf_show_error();
85 }
86
87 // Do the tokens match?
Derek Jones95b183ad2010-08-31 09:42:39 -050088 if ($_POST[$this->csrf_token_name] != $_COOKIE[$this->csrf_cookie_name])
Derek Jonese701d762010-03-02 18:17:01 -060089 {
90 $this->csrf_show_error();
91 }
92
93 // We kill this since we're done and we don't want to polute the _POST array
94 unset($_POST[$this->csrf_token_name]);
Barry Mienydd671972010-10-04 16:33:58 +020095
Derek Jonesb3f10a22010-07-25 19:11:26 -050096 // Nothing should last forever
Derek Jones95b183ad2010-08-31 09:42:39 -050097 unset($_COOKIE[$this->csrf_cookie_name]);
Derek Jonesb3f10a22010-07-25 19:11:26 -050098 $this->_csrf_set_hash();
99 $this->csrf_set_cookie();
Derek Jonese701d762010-03-02 18:17:01 -0600100
101 log_message('debug', "CSRF token verified ");
102 }
Barry Mienydd671972010-10-04 16:33:58 +0200103
Derek Jonese701d762010-03-02 18:17:01 -0600104 // --------------------------------------------------------------------
Barry Mienydd671972010-10-04 16:33:58 +0200105
Derek Jonese701d762010-03-02 18:17:01 -0600106 /**
107 * Set Cross Site Request Forgery Protection Cookie
108 *
109 * @access public
110 * @return null
111 */
112 function csrf_set_cookie()
113 {
Derek Jonese701d762010-03-02 18:17:01 -0600114 $expire = time() + $this->csrf_expire;
115
Derek Jones95b183ad2010-08-31 09:42:39 -0500116 setcookie($this->csrf_cookie_name, $this->csrf_hash, $expire, config_item('cookie_path'), config_item('cookie_domain'), 0);
Barry Mienydd671972010-10-04 16:33:58 +0200117
118 log_message('debug', "CRSF cookie Set");
Derek Jonese701d762010-03-02 18:17:01 -0600119 }
Barry Mienydd671972010-10-04 16:33:58 +0200120
Derek Jonese701d762010-03-02 18:17:01 -0600121 // --------------------------------------------------------------------
Barry Mienydd671972010-10-04 16:33:58 +0200122
Derek Jonese701d762010-03-02 18:17:01 -0600123 /**
124 * Set Cross Site Request Forgery Protection Cookie
125 *
126 * @access public
127 * @return null
128 */
129 function _csrf_set_hash()
130 {
131 if ($this->csrf_hash == '')
132 {
133 // If the cookie exists we will use it's value. We don't necessarily want to regenerate it with
134 // each page load since a page could contain embedded sub-pages causing this feature to fail
Derek Jones95b183ad2010-08-31 09:42:39 -0500135 if (isset($_COOKIE[$this->csrf_cookie_name]) AND $_COOKIE[$this->csrf_cookie_name] != '')
Derek Jonese701d762010-03-02 18:17:01 -0600136 {
Derek Jones95b183ad2010-08-31 09:42:39 -0500137 $this->csrf_hash = $_COOKIE[$this->csrf_cookie_name];
Derek Jonese701d762010-03-02 18:17:01 -0600138 }
139 else
140 {
141 $this->csrf_hash = md5(uniqid(rand(), TRUE));
142 }
143 }
Derek Allard958543a2010-07-22 14:10:26 -0400144
Derek Jonese701d762010-03-02 18:17:01 -0600145 return $this->csrf_hash;
146 }
147
148 // --------------------------------------------------------------------
Barry Mienydd671972010-10-04 16:33:58 +0200149
Derek Jonese701d762010-03-02 18:17:01 -0600150 /**
151 * Show CSRF Error
152 *
153 * @access public
154 * @return null
155 */
156 function csrf_show_error()
157 {
158 show_error('The action you have requested is not allowed.');
159 }
160
161 // --------------------------------------------------------------------
Barry Mienydd671972010-10-04 16:33:58 +0200162
Derek Jonese701d762010-03-02 18:17:01 -0600163 /**
164 * XSS Clean
165 *
166 * Sanitizes data so that Cross Site Scripting Hacks can be
167 * prevented. This function does a fair amount of work but
168 * it is extremely thorough, designed to prevent even the
169 * most obscure XSS attempts. Nothing is ever 100% foolproof,
170 * of course, but I haven't been able to get anything passed
171 * the filter.
172 *
173 * Note: This function should only be used to deal with data
174 * upon submission. It's not something that should
175 * be used for general runtime processing.
176 *
177 * This function was based in part on some code and ideas I
178 * got from Bitflux: http://channel.bitflux.ch/wiki/XSS_Prevention
179 *
180 * To help develop this script I used this great list of
181 * vulnerabilities along with a few other hacks I've
182 * harvested from examining vulnerabilities in other programs:
183 * http://ha.ckers.org/xss.html
184 *
185 * @access public
186 * @param mixed string or array
187 * @return string
188 */
189 function xss_clean($str, $is_image = FALSE)
190 {
191 /*
192 * Is the string an array?
193 *
194 */
195 if (is_array($str))
196 {
197 while (list($key) = each($str))
198 {
199 $str[$key] = $this->xss_clean($str[$key]);
200 }
Barry Mienydd671972010-10-04 16:33:58 +0200201
Derek Jonese701d762010-03-02 18:17:01 -0600202 return $str;
203 }
204
205 /*
206 * Remove Invisible Characters
207 */
Greg Aker757dda62010-04-14 19:06:19 -0500208 $str = remove_invisible_characters($str);
Derek Jonese701d762010-03-02 18:17:01 -0600209
210 /*
211 * Protect GET variables in URLs
212 */
Barry Mienydd671972010-10-04 16:33:58 +0200213
Derek Jonese701d762010-03-02 18:17:01 -0600214 // 901119URL5918AMP18930PROTECT8198
Barry Mienydd671972010-10-04 16:33:58 +0200215
Derek Jonese701d762010-03-02 18:17:01 -0600216 $str = preg_replace('|\&([a-z\_0-9\-]+)\=([a-z\_0-9\-]+)|i', $this->xss_hash()."\\1=\\2", $str);
217
218 /*
219 * Validate standard character entities
220 *
221 * Add a semicolon if missing. We do this to enable
222 * the conversion of entities to ASCII later.
223 *
224 */
225 $str = preg_replace('#(&\#?[0-9a-z]{2,})([\x00-\x20])*;?#i', "\\1;\\2", $str);
226
227 /*
Barry Mienydd671972010-10-04 16:33:58 +0200228 * Validate UTF16 two byte encoding (x00)
Derek Jonese701d762010-03-02 18:17:01 -0600229 *
230 * Just as above, adds a semicolon if missing.
231 *
232 */
233 $str = preg_replace('#(&\#x?)([0-9A-F]+);?#i',"\\1\\2;",$str);
234
235 /*
236 * Un-Protect GET variables in URLs
237 */
238 $str = str_replace($this->xss_hash(), '&', $str);
239
240 /*
241 * URL Decode
242 *
243 * Just in case stuff like this is submitted:
244 *
245 * <a href="http://%77%77%77%2E%67%6F%6F%67%6C%65%2E%63%6F%6D">Google</a>
246 *
247 * Note: Use rawurldecode() so it does not remove plus signs
248 *
249 */
250 $str = rawurldecode($str);
Barry Mienydd671972010-10-04 16:33:58 +0200251
Derek Jonese701d762010-03-02 18:17:01 -0600252 /*
Barry Mienydd671972010-10-04 16:33:58 +0200253 * Convert character entities to ASCII
Derek Jonese701d762010-03-02 18:17:01 -0600254 *
255 * This permits our tests below to work reliably.
256 * We only convert entities that are within tags since
257 * these are the ones that will pose security problems.
258 *
259 */
260
261 $str = preg_replace_callback("/[a-z]+=([\'\"]).*?\\1/si", array($this, '_convert_attribute'), $str);
Barry Mienydd671972010-10-04 16:33:58 +0200262
Derek Jonese701d762010-03-02 18:17:01 -0600263 $str = preg_replace_callback("/<\w+.*?(?=>|<|$)/si", array($this, '_decode_entity'), $str);
264
265 /*
266 * Remove Invisible Characters Again!
267 */
Greg Aker757dda62010-04-14 19:06:19 -0500268 $str = remove_invisible_characters($str);
Barry Mienydd671972010-10-04 16:33:58 +0200269
Derek Jonese701d762010-03-02 18:17:01 -0600270 /*
271 * Convert all tabs to spaces
272 *
273 * This prevents strings like this: ja vascript
274 * NOTE: we deal with spaces between characters later.
275 * NOTE: preg_replace was found to be amazingly slow here on large blocks of data,
276 * so we use str_replace.
277 *
278 */
Barry Mienydd671972010-10-04 16:33:58 +0200279
Derek Jonese701d762010-03-02 18:17:01 -0600280 if (strpos($str, "\t") !== FALSE)
281 {
282 $str = str_replace("\t", ' ', $str);
283 }
Barry Mienydd671972010-10-04 16:33:58 +0200284
Derek Jonese701d762010-03-02 18:17:01 -0600285 /*
286 * Capture converted string for later comparison
287 */
288 $converted_string = $str;
Barry Mienydd671972010-10-04 16:33:58 +0200289
Derek Jonese701d762010-03-02 18:17:01 -0600290 /*
291 * Not Allowed Under Any Conditions
292 */
Barry Mienydd671972010-10-04 16:33:58 +0200293
Derek Jonese701d762010-03-02 18:17:01 -0600294 foreach ($this->never_allowed_str as $key => $val)
295 {
Barry Mienydd671972010-10-04 16:33:58 +0200296 $str = str_replace($key, $val, $str);
Derek Jonese701d762010-03-02 18:17:01 -0600297 }
Barry Mienydd671972010-10-04 16:33:58 +0200298
Derek Jonese701d762010-03-02 18:17:01 -0600299 foreach ($this->never_allowed_regex as $key => $val)
300 {
Barry Mienydd671972010-10-04 16:33:58 +0200301 $str = preg_replace("#".$key."#i", $val, $str);
Derek Jonese701d762010-03-02 18:17:01 -0600302 }
303
304 /*
305 * Makes PHP tags safe
306 *
307 * Note: XML tags are inadvertently replaced too:
308 *
309 * <?xml
310 *
311 * But it doesn't seem to pose a problem.
312 *
313 */
314 if ($is_image === TRUE)
315 {
316 // Images have a tendency to have the PHP short opening and closing tags every so often
317 // so we skip those and only do the long opening tags.
318 $str = preg_replace('/<\?(php)/i', "&lt;?\\1", $str);
319 }
320 else
321 {
322 $str = str_replace(array('<?', '?'.'>'), array('&lt;?', '?&gt;'), $str);
323 }
Barry Mienydd671972010-10-04 16:33:58 +0200324
Derek Jonese701d762010-03-02 18:17:01 -0600325 /*
326 * Compact any exploded words
327 *
328 * This corrects words like: j a v a s c r i p t
329 * These words are compacted back to their correct state.
330 *
331 */
332 $words = array('javascript', 'expression', 'vbscript', 'script', 'applet', 'alert', 'document', 'write', 'cookie', 'window');
333 foreach ($words as $word)
334 {
335 $temp = '';
Barry Mienydd671972010-10-04 16:33:58 +0200336
Derek Jonese701d762010-03-02 18:17:01 -0600337 for ($i = 0, $wordlen = strlen($word); $i < $wordlen; $i++)
338 {
339 $temp .= substr($word, $i, 1)."\s*";
340 }
341
342 // We only want to do this when it is followed by a non-word character
343 // That way valid stuff like "dealer to" does not become "dealerto"
344 $str = preg_replace_callback('#('.substr($temp, 0, -3).')(\W)#is', array($this, '_compact_exploded_words'), $str);
345 }
Barry Mienydd671972010-10-04 16:33:58 +0200346
Derek Jonese701d762010-03-02 18:17:01 -0600347 /*
348 * Remove disallowed Javascript in links or img tags
349 * We used to do some version comparisons and use of stripos for PHP5, but it is dog slow compared
350 * to these simplified non-capturing preg_match(), especially if the pattern exists in the string
351 */
352 do
353 {
354 $original = $str;
Barry Mienydd671972010-10-04 16:33:58 +0200355
Derek Jonese701d762010-03-02 18:17:01 -0600356 if (preg_match("/<a/i", $str))
357 {
358 $str = preg_replace_callback("#<a\s+([^>]*?)(>|$)#si", array($this, '_js_link_removal'), $str);
359 }
Barry Mienydd671972010-10-04 16:33:58 +0200360
Derek Jonese701d762010-03-02 18:17:01 -0600361 if (preg_match("/<img/i", $str))
362 {
363 $str = preg_replace_callback("#<img\s+([^>]*?)(\s?/?>|$)#si", array($this, '_js_img_removal'), $str);
364 }
Barry Mienydd671972010-10-04 16:33:58 +0200365
Derek Jonese701d762010-03-02 18:17:01 -0600366 if (preg_match("/script/i", $str) OR preg_match("/xss/i", $str))
367 {
368 $str = preg_replace("#<(/*)(script|xss)(.*?)\>#si", '[removed]', $str);
369 }
370 }
371 while($original != $str);
372
373 unset($original);
374
375 /*
376 * Remove JavaScript Event Handlers
377 *
378 * Note: This code is a little blunt. It removes
379 * the event handler and anything up to the closing >,
380 * but it's unlikely to be a problem.
381 *
382 */
383 $event_handlers = array('[^a-z_\-]on\w*','xmlns');
384
385 if ($is_image === TRUE)
386 {
387 /*
Barry Mienydd671972010-10-04 16:33:58 +0200388 * Adobe Photoshop puts XML metadata into JFIF images, including namespacing,
Derek Jonese701d762010-03-02 18:17:01 -0600389 * so we have to allow this for images. -Paul
390 */
391 unset($event_handlers[array_search('xmlns', $event_handlers)]);
392 }
393
394 $str = preg_replace("#<([^><]+?)(".implode('|', $event_handlers).")(\s*=\s*[^><]*)([><]*)#i", "<\\1\\4", $str);
Barry Mienydd671972010-10-04 16:33:58 +0200395
Derek Jonese701d762010-03-02 18:17:01 -0600396 /*
397 * Sanitize naughty HTML elements
398 *
399 * If a tag containing any of the words in the list
400 * below is found, the tag gets converted to entities.
401 *
402 * So this: <blink>
403 * Becomes: &lt;blink&gt;
404 *
405 */
406 $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';
407 $str = preg_replace_callback('#<(/*\s*)('.$naughty.')([^><]*)([><]*)#is', array($this, '_sanitize_naughty_html'), $str);
408
409 /*
410 * Sanitize naughty scripting elements
411 *
412 * Similar to above, only instead of looking for
413 * tags it looks for PHP and JavaScript commands
414 * that are disallowed. Rather than removing the
415 * code, it simply converts the parenthesis to entities
416 * rendering the code un-executable.
417 *
418 * For example: eval('some code')
419 * Becomes: eval&#40;'some code'&#41;
420 *
421 */
422 $str = preg_replace('#(alert|cmd|passthru|eval|exec|expression|system|fopen|fsockopen|file|file_get_contents|readfile|unlink)(\s*)\((.*?)\)#si', "\\1\\2&#40;\\3&#41;", $str);
Barry Mienydd671972010-10-04 16:33:58 +0200423
Derek Jonese701d762010-03-02 18:17:01 -0600424 /*
425 * Final clean up
426 *
427 * This adds a bit of extra precaution in case
428 * something got through the above filters
429 *
430 */
431 foreach ($this->never_allowed_str as $key => $val)
432 {
Barry Mienydd671972010-10-04 16:33:58 +0200433 $str = str_replace($key, $val, $str);
Derek Jonese701d762010-03-02 18:17:01 -0600434 }
Barry Mienydd671972010-10-04 16:33:58 +0200435
Derek Jonese701d762010-03-02 18:17:01 -0600436 foreach ($this->never_allowed_regex as $key => $val)
437 {
438 $str = preg_replace("#".$key."#i", $val, $str);
439 }
440
441 /*
442 * Images are Handled in a Special Way
443 * - Essentially, we want to know that after all of the character conversion is done whether
444 * any unwanted, likely XSS, code was found. If not, we return TRUE, as the image is clean.
445 * However, if the string post-conversion does not matched the string post-removal of XSS,
446 * then it fails, as there was unwanted XSS code found and removed/changed during processing.
447 */
448
449 if ($is_image === TRUE)
450 {
451 if ($str == $converted_string)
452 {
453 return TRUE;
454 }
455 else
456 {
457 return FALSE;
458 }
459 }
Barry Mienydd671972010-10-04 16:33:58 +0200460
Derek Jonese701d762010-03-02 18:17:01 -0600461 log_message('debug', "XSS Filtering completed");
462 return $str;
463 }
464
465 // --------------------------------------------------------------------
Barry Mienydd671972010-10-04 16:33:58 +0200466
Derek Jonese701d762010-03-02 18:17:01 -0600467 /**
468 * Random Hash for protecting URLs
469 *
470 * @access public
471 * @return string
472 */
473 function xss_hash()
Barry Mienydd671972010-10-04 16:33:58 +0200474 {
Derek Jonese701d762010-03-02 18:17:01 -0600475 if ($this->xss_hash == '')
476 {
477 if (phpversion() >= 4.2)
478 mt_srand();
479 else
480 mt_srand(hexdec(substr(md5(microtime()), -8)) & 0x7fffffff);
Barry Mienydd671972010-10-04 16:33:58 +0200481
Derek Jonese701d762010-03-02 18:17:01 -0600482 $this->xss_hash = md5(time() + mt_rand(0, 1999999999));
483 }
Barry Mienydd671972010-10-04 16:33:58 +0200484
Derek Jonese701d762010-03-02 18:17:01 -0600485 return $this->xss_hash;
486 }
487
488 // --------------------------------------------------------------------
Barry Mienydd671972010-10-04 16:33:58 +0200489
Derek Jonese701d762010-03-02 18:17:01 -0600490 /**
Derek Jonese701d762010-03-02 18:17:01 -0600491 * Compact Exploded Words
492 *
493 * Callback function for xss_clean() to remove whitespace from
494 * things like j a v a s c r i p t
495 *
496 * @access public
497 * @param type
498 * @return type
499 */
500 function _compact_exploded_words($matches)
501 {
502 return preg_replace('/\s+/s', '', $matches[1]).$matches[2];
503 }
Barry Mienydd671972010-10-04 16:33:58 +0200504
Derek Jonese701d762010-03-02 18:17:01 -0600505 // --------------------------------------------------------------------
Barry Mienydd671972010-10-04 16:33:58 +0200506
Derek Jonese701d762010-03-02 18:17:01 -0600507 /**
508 * Sanitize Naughty HTML
509 *
510 * Callback function for xss_clean() to remove naughty HTML elements
511 *
512 * @access private
513 * @param array
514 * @return string
515 */
516 function _sanitize_naughty_html($matches)
517 {
518 // encode opening brace
519 $str = '&lt;'.$matches[1].$matches[2].$matches[3];
Barry Mienydd671972010-10-04 16:33:58 +0200520
Derek Jonese701d762010-03-02 18:17:01 -0600521 // encode captured opening or closing brace to prevent recursive vectors
522 $str .= str_replace(array('>', '<'), array('&gt;', '&lt;'), $matches[4]);
Barry Mienydd671972010-10-04 16:33:58 +0200523
Derek Jonese701d762010-03-02 18:17:01 -0600524 return $str;
525 }
Barry Mienydd671972010-10-04 16:33:58 +0200526
Derek Jonese701d762010-03-02 18:17:01 -0600527 // --------------------------------------------------------------------
Barry Mienydd671972010-10-04 16:33:58 +0200528
Derek Jonese701d762010-03-02 18:17:01 -0600529 /**
530 * JS Link Removal
531 *
532 * Callback function for xss_clean() to sanitize links
533 * This limits the PCRE backtracks, making it more performance friendly
534 * and prevents PREG_BACKTRACK_LIMIT_ERROR from being triggered in
535 * PHP 5.2+ on link-heavy strings
536 *
537 * @access private
538 * @param array
539 * @return string
540 */
541 function _js_link_removal($match)
542 {
543 $attributes = $this->_filter_attributes(str_replace(array('<', '>'), '', $match[1]));
544 return str_replace($match[1], preg_replace("#href=.*?(alert\(|alert&\#40;|javascript\:|charset\=|window\.|document\.|\.cookie|<script|<xss|base64\s*,)#si", "", $attributes), $match[0]);
545 }
Barry Mienydd671972010-10-04 16:33:58 +0200546
Derek Jonese701d762010-03-02 18:17:01 -0600547 /**
548 * JS Image Removal
549 *
550 * Callback function for xss_clean() to sanitize image tags
551 * This limits the PCRE backtracks, making it more performance friendly
552 * and prevents PREG_BACKTRACK_LIMIT_ERROR from being triggered in
553 * PHP 5.2+ on image tag heavy strings
554 *
555 * @access private
556 * @param array
557 * @return string
558 */
559 function _js_img_removal($match)
560 {
561 $attributes = $this->_filter_attributes(str_replace(array('<', '>'), '', $match[1]));
562 return str_replace($match[1], preg_replace("#src=.*?(alert\(|alert&\#40;|javascript\:|charset\=|window\.|document\.|\.cookie|<script|<xss|base64\s*,)#si", "", $attributes), $match[0]);
563 }
564
565 // --------------------------------------------------------------------
Barry Mienydd671972010-10-04 16:33:58 +0200566
Derek Jonese701d762010-03-02 18:17:01 -0600567 /**
568 * Attribute Conversion
569 *
570 * Used as a callback for XSS Clean
571 *
572 * @access public
573 * @param array
574 * @return string
575 */
576 function _convert_attribute($match)
577 {
578 return str_replace(array('>', '<', '\\'), array('&gt;', '&lt;', '\\\\'), $match[0]);
579 }
Barry Mienydd671972010-10-04 16:33:58 +0200580
Derek Jonese701d762010-03-02 18:17:01 -0600581 // --------------------------------------------------------------------
582
583 /**
584 * Filter Attributes
585 *
586 * Filters tag attributes for consistency and safety
587 *
588 * @access public
589 * @param string
590 * @return string
591 */
592 function _filter_attributes($str)
593 {
594 $out = '';
595
596 if (preg_match_all('#\s*[a-z\-]+\s*=\s*(\042|\047)([^\\1]*?)\\1#is', $str, $matches))
597 {
598 foreach ($matches[0] as $match)
599 {
600 $out .= preg_replace("#/\*.*?\*/#s", '', $match);
601 }
602 }
603
604 return $out;
605 }
606
607 // --------------------------------------------------------------------
608
609 /**
610 * HTML Entity Decode Callback
611 *
612 * Used as a callback for XSS Clean
613 *
614 * @access public
615 * @param array
616 * @return string
617 */
618 function _decode_entity($match)
619 {
Derek Jonesa0911472010-03-30 10:33:09 -0500620 return $this->entity_decode($match[0], strtoupper(config_item('charset')));
Derek Jonese701d762010-03-02 18:17:01 -0600621 }
622
623 // --------------------------------------------------------------------
624
625 /**
Derek Jonesa0911472010-03-30 10:33:09 -0500626 * HTML Entities Decode
627 *
628 * This function is a replacement for html_entity_decode()
629 *
630 * In some versions of PHP the native function does not work
631 * when UTF-8 is the specified character set, so this gives us
632 * a work-around. More info here:
633 * http://bugs.php.net/bug.php?id=25670
634 *
635 * NOTE: html_entity_decode() has a bug in some PHP versions when UTF-8 is the
636 * character set, and the PHP developers said they were not back porting the
637 * fix to versions other than PHP 5.x.
638 *
639 * @access public
640 * @param string
641 * @param string
642 * @return string
643 */
644 function entity_decode($str, $charset='UTF-8')
645 {
646 if (stristr($str, '&') === FALSE) return $str;
Barry Mienydd671972010-10-04 16:33:58 +0200647
Derek Jonesa0911472010-03-30 10:33:09 -0500648 // The reason we are not using html_entity_decode() by itself is because
649 // while it is not technically correct to leave out the semicolon
650 // at the end of an entity most browsers will still interpret the entity
651 // correctly. html_entity_decode() does not convert entities without
652 // semicolons, so we are left with our own little solution here. Bummer.
Barry Mienydd671972010-10-04 16:33:58 +0200653
Derek Jonesa0911472010-03-30 10:33:09 -0500654 if (function_exists('html_entity_decode') && (strtolower($charset) != 'utf-8' OR is_php('5.0.0')))
655 {
656 $str = html_entity_decode($str, ENT_COMPAT, $charset);
657 $str = preg_replace('~&#x(0*[0-9a-f]{2,5})~ei', 'chr(hexdec("\\1"))', $str);
658 return preg_replace('~&#([0-9]{2,4})~e', 'chr(\\1)', $str);
659 }
Barry Mienydd671972010-10-04 16:33:58 +0200660
Derek Jonesa0911472010-03-30 10:33:09 -0500661 // Numeric Entities
662 $str = preg_replace('~&#x(0*[0-9a-f]{2,5});{0,1}~ei', 'chr(hexdec("\\1"))', $str);
663 $str = preg_replace('~&#([0-9]{2,4});{0,1}~e', 'chr(\\1)', $str);
Barry Mienydd671972010-10-04 16:33:58 +0200664
Derek Jonesa0911472010-03-30 10:33:09 -0500665 // Literal Entities - Slightly slow so we do another check
666 if (stristr($str, '&') === FALSE)
667 {
668 $str = strtr($str, array_flip(get_html_translation_table(HTML_ENTITIES)));
669 }
Barry Mienydd671972010-10-04 16:33:58 +0200670
Derek Jonesa0911472010-03-30 10:33:09 -0500671 return $str;
672 }
Barry Mienydd671972010-10-04 16:33:58 +0200673
Derek Jonesa0911472010-03-30 10:33:09 -0500674 // --------------------------------------------------------------------
Barry Mienydd671972010-10-04 16:33:58 +0200675
Derek Jonesa0911472010-03-30 10:33:09 -0500676 /**
Derek Jonese701d762010-03-02 18:17:01 -0600677 * Filename Security
678 *
679 * @access public
680 * @param string
681 * @return string
682 */
683 function sanitize_filename($str)
684 {
685 $bad = array(
686 "../",
687 "./",
688 "<!--",
689 "-->",
690 "<",
691 ">",
692 "'",
693 '"',
694 '&',
695 '$',
696 '#',
697 '{',
698 '}',
699 '[',
700 ']',
701 '=',
702 ';',
703 '?',
704 '/',
705 "%20",
706 "%22",
707 "%3c", // <
Barry Mienydd671972010-10-04 16:33:58 +0200708 "%253c", // <
709 "%3e", // >
710 "%0e", // >
711 "%28", // (
712 "%29", // )
713 "%2528", // (
714 "%26", // &
715 "%24", // $
716 "%3f", // ?
717 "%3b", // ;
Derek Jonese701d762010-03-02 18:17:01 -0600718 "%3d" // =
719 );
720
721 return stripslashes(str_replace($bad, '', $str));
722 }
723
724}
725// END Security Class
726
727/* End of file Security.php */
728/* Location: ./system/libraries/Security.php */