blob: 41d77a97a2a086586fbe1d2890de12ea92065db6 [file] [log] [blame]
adminb0dd10f2006-08-25 17:25:49 +00001<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
2/**
3 * Code Igniter
4 *
5 * An open source application development framework for PHP 4.3.2 or newer
6 *
7 * @package CodeIgniter
8 * @author Rick Ellis
9 * @copyright Copyright (c) 2006, pMachine, Inc.
10 * @license http://www.codeignitor.com/user_guide/license.html
11 * @link http://www.codeigniter.com
12 * @since Version 1.0
13 * @filesource
14 */
15
16// ------------------------------------------------------------------------
17
18/**
19 * Input Class
20 *
21 * Pre-processes global input data for security
22 *
23 * @package CodeIgniter
24 * @subpackage Libraries
25 * @category Input
26 * @author Rick Ellis
27 * @link http://www.codeigniter.com/user_guide/libraries/input.html
28 */
29class CI_Input {
30 var $use_xss_clean = FALSE;
31 var $ip_address = FALSE;
32 var $user_agent = FALSE;
33 var $allow_get_array = FALSE;
34
35 /**
36 * Constructor
37 *
38 * Sets whether to globally enable the XSS processing
39 * and whether to allow the $_GET array
40 *
41 * @access public
42 */
43 function CI_Input()
44 {
admin33de9a12006-09-28 06:50:16 +000045 $CFG =& _load_class('Config');
adminb0dd10f2006-08-25 17:25:49 +000046 $this->use_xss_clean = ($CFG->item('global_xss_filtering') === TRUE) ? TRUE : FALSE;
47 $this->allow_get_array = ($CFG->item('enable_query_strings') === TRUE) ? TRUE : FALSE;
48
49 log_message('debug', "Input Class Initialized");
50 $this->_sanitize_globals();
51 }
52 // END CI_Input()
53
54 // --------------------------------------------------------------------
55
56 /**
57 * Sanitize Globals
58 *
59 * This function does the folowing:
60 *
61 * Unsets $_GET data (if query strings are not enabled)
62 *
63 * Unsets all globals if register_globals is enabled
64 *
65 * Standardizes newline characters to \n
66 *
67 * @access private
68 * @return void
69 */
70 function _sanitize_globals()
71 {
72 // Unset globals. This is effectively the same as register_globals = off
73 foreach (array($_GET, $_POST, $_COOKIE) as $global)
74 {
75 if ( ! is_array($global))
76 {
77 unset($$global);
78 }
79 else
80 {
81 foreach ($global as $key => $val)
82 {
83 unset($$key);
84 }
85 }
86 }
87
88 // Is $_GET data allowed?
89 if ($this->allow_get_array == FALSE)
90 {
91 $_GET = array();
92 }
93
94 // Clean $_POST Data
95 if (is_array($_POST) AND count($_POST) > 0)
96 {
97 foreach($_POST as $key => $val)
98 {
99 if (is_array($val))
100 {
101 foreach($val as $k => $v)
102 {
103 $_POST[$this->_clean_input_keys($key)][$this->_clean_input_keys($k)] = $this->_clean_input_data($v);
104 }
105 }
106 else
107 {
108 $_POST[$this->_clean_input_keys($key)] = $this->_clean_input_data($val);
109 }
110 }
111 }
112
113 // Clean $_COOKIE Data
114 if (is_array($_COOKIE) AND count($_COOKIE) > 0)
115 {
116 foreach($_COOKIE as $key => $val)
117 {
118 $_COOKIE[$this->_clean_input_keys($key)] = $this->_clean_input_data($val);
119 }
120 }
121
122 log_message('debug', "Global POST and COOKIE data sanitized");
123 }
124 // END _sanitize_globals()
125
126 // --------------------------------------------------------------------
127
128 /**
129 * Clean Intput Data
130 *
131 * This is a helper function. It escapes data and
132 * standardizes newline characters to \n
133 *
134 * @access private
135 * @param string
136 * @return string
137 */
138 function _clean_input_data($str)
139 {
140 if (is_array($str))
141 {
142 $new_array = array();
143 foreach ($str as $key => $val)
144 {
145 $new_array[$key] = $this->_clean_input_data($val);
146 }
147 return $new_array;
148 }
149
150 if ($this->use_xss_clean === TRUE)
151 {
152 $str = $this->xss_clean($str);
153 }
154
155 return preg_replace("/\015\012|\015|\012/", "\n", $str);
156 }
157 // END _clean_input_data()
158
159 // --------------------------------------------------------------------
160
161 /**
162 * Clean Keys
163 *
164 * This is a helper function. To prevent malicious users
165 * from trying to exploit keys we make sure that keys are
166 * only named with alpha-numeric text and a few other items.
167 *
168 * @access private
169 * @param string
170 * @return string
171 */
172 function _clean_input_keys($str)
173 {
174 if ( ! preg_match("/^[a-z0-9:_\/-]+$/i", $str))
175 {
176 exit('Disallowed Key Characters: '.$str);
177 }
178
179 if ( ! get_magic_quotes_gpc())
180 {
181 return addslashes($str);
182 }
183
184 return $str;
185 }
186 // END _clean_input_keys()
187
188 // --------------------------------------------------------------------
189
190 /**
191 * Fetch an item from the POST array
192 *
193 * @access public
194 * @param string
195 * @return string
196 */
197 function post($index = '', $xss_clean = FALSE)
198 {
199 if ( ! isset($_POST[$index]))
200 {
201 return FALSE;
202 }
203 else
204 {
205 if ($xss_clean === TRUE)
206 {
207 return $this->xss_clean($_POST[$index]);
208 }
209 else
210 {
211 return $_POST[$index];
212 }
213 }
214 }
215 // END post()
216
217 // --------------------------------------------------------------------
218
219 /**
220 * Fetch an item from the COOKIE array
221 *
222 * @access public
223 * @param string
224 * @return string
225 */
226 function cookie($index = '', $xss_clean = FALSE)
227 {
228 if ( ! isset($_COOKIE[$index]))
229 {
230 return FALSE;
231 }
232 else
admin2fcd16b2006-10-03 16:41:54 +0000233 {
adminb0dd10f2006-08-25 17:25:49 +0000234 if ($xss_clean === TRUE)
235 {
admin2fcd16b2006-10-03 16:41:54 +0000236 if (is_array($_COOKIE[$index]))
237 {
238 $cookie = array();
239 foreach($_COOKIE[$index] as $key => $val)
240 {
241 $cookie[$key] = $this->xss_clean($val);
242 }
243
244 return $cookie;
245 }
246 else
247 {
248 return $this->xss_clean($_COOKIE[$index]);
249 }
adminb0dd10f2006-08-25 17:25:49 +0000250 }
251 else
252 {
253 return $_COOKIE[$index];
254 }
255 }
256 }
257 // END cookie()
258
259 // --------------------------------------------------------------------
260
261 /**
262 * Fetch the IP Address
263 *
264 * @access public
265 * @return string
266 */
267 function ip_address()
268 {
269 if ($this->ip_address !== FALSE)
270 {
271 return $this->ip_address;
272 }
273
274 $cip = (isset($_SERVER['HTTP_CLIENT_IP']) AND $_SERVER['HTTP_CLIENT_IP'] != "") ? $_SERVER['HTTP_CLIENT_IP'] : FALSE;
275 $rip = (isset($_SERVER['REMOTE_ADDR']) AND $_SERVER['REMOTE_ADDR'] != "") ? $_SERVER['REMOTE_ADDR'] : FALSE;
276 $fip = (isset($_SERVER['HTTP_X_FORWARDED_FOR']) AND $_SERVER['HTTP_X_FORWARDED_FOR'] != "") ? $_SERVER['HTTP_X_FORWARDED_FOR'] : FALSE;
277
278 if ($cip && $rip) $this->ip_address = $cip;
279 elseif ($rip) $this->ip_address = $rip;
280 elseif ($cip) $this->ip_address = $cip;
281 elseif ($fip) $this->ip_address = $fip;
282
283 if (strstr($this->ip_address, ','))
284 {
285 $x = explode(',', $this->ip_address);
286 $this->ip_address = end($x);
287 }
288
289 if ( ! $this->valid_ip($this->ip_address))
290 {
291 $this->ip_address = '0.0.0.0';
292 }
293
294 unset($cip);
295 unset($rip);
296 unset($fip);
297
298 return $this->ip_address;
299 }
300 // END ip_address()
301
302 // --------------------------------------------------------------------
303
304 /**
305 * Validate IP Address
306 *
307 * @access public
308 * @param string
309 * @return string
310 */
311 function valid_ip($ip)
312 {
313 return ( ! preg_match( "/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/", $ip)) ? FALSE : TRUE;
314 }
315 // END valid_ip()
316
317 // --------------------------------------------------------------------
318
319 /**
320 * User Agent
321 *
322 * @access public
323 * @return string
324 */
325 function user_agent()
326 {
327 if ($this->user_agent !== FALSE)
328 {
329 return $this->user_agent;
330 }
331
332 $this->user_agent = ( ! isset($_SERVER['HTTP_USER_AGENT'])) ? FALSE : $_SERVER['HTTP_USER_AGENT'];
333
334 return $this->user_agent;
335 }
336 // END user_agent()
337
338 // --------------------------------------------------------------------
339
340 /**
341 * XSS Clean
342 *
343 * Sanitizes data so that Cross Site Scripting Hacks can be
344 * prevented.Ê This function does a fair amount of work but
345 * it is extremely thorough, designed to prevent even the
346 * most obscure XSS attempts.Ê Nothing is ever 100% foolproof,
347 * of course, but I haven't been able to get anything passed
348 * the filter.
349 *
350 * Note: This function should only be used to deal with data
351 * upon submission.Ê It's not something that should
352 * be used for general runtime processing.
353 *
354 * This function was based in part on some code and ideas I
355 * got from Bitflux: http://blog.bitflux.ch/wiki/XSS_Prevention
356 *
357 * To help develop this script I used this great list of
358 * vulnerabilities along with a few other hacks I've
359 * harvested from examining vulnerabilities in other programs:
360 * http://ha.ckers.org/xss.html
361 *
362 * @access public
363 * @param string
364 * @return string
365 */
366 function xss_clean($str, $charset = 'ISO-8859-1')
367 {
368 /*
369 * Remove Null Characters
370 *
371 * This prevents sandwiching null characters
372 * between ascii characters, like Java\0script.
373 *
374 */
375 $str = preg_replace('/\0+/', '', $str);
376 $str = preg_replace('/(\\\\0)+/', '', $str);
377
378 /*
379 * Validate standard character entites
380 *
381 * Add a semicolon if missing. We do this to enable
382 * the conversion of entities to ASCII later.
383 *
384 */
385 $str = preg_replace('#(&\#*\w+)[\x00-\x20]+;#u',"\\1;",$str);
386
387 /*
388 * Validate UTF16 two byte encodeing (x00)
389 *
390 * Just as above, adds a semicolon if missing.
391 *
392 */
393 $str = preg_replace('#(&\#x*)([0-9A-F]+);*#iu',"\\1\\2;",$str);
394
395 /*
396 * URL Decode
397 *
398 * Just in case stuff like this is submitted:
399 *
400 * <a href="http://%77%77%77%2E%67%6F%6F%67%6C%65%2E%63%6F%6D">Google</a>
401 *
402 * Note: Normally urldecode() would be easier but it removes plus signs
403 *
404 */
405 $str = preg_replace("/%u0([a-z0-9]{3})/i", "&#x\\1;", $str);
406 $str = preg_replace("/%([a-z0-9]{2})/i", "&#x\\1;", $str);
407
408 /*
409 * Convert character entities to ASCII
410 *
411 * This permits our tests below to work reliably.
412 * We only convert entities that are within tags since
413 * these are the ones that will pose security problems.
414 *
415 */
416
417 if (preg_match_all("/<(.+?)>/si", $str, $matches))
418 {
419 for ($i = 0; $i < count($matches['0']); $i++)
420 {
421 $str = str_replace($matches['1'][$i],
422 $this->_html_entity_decode($matches['1'][$i], $charset),
423 $str);
424 }
425 }
426
427 /*
428 * Convert all tabs to spaces
429 *
430 * This prevents strings like this: ja vascript
431 * Note: we deal with spaces between characters later.
432 *
433 */
434 $str = preg_replace("#\t+#", " ", $str);
435
436 /*
437 * Makes PHP tags safe
438 *
439 * Note: XML tags are inadvertently replaced too:
440 *
441 * <?xml
442 *
443 * But it doesn't seem to pose a problem.
444 *
445 */
adminbc042dd2006-09-21 02:46:59 +0000446 $str = str_replace(array('<?php', '<?PHP', '<?', '?>'), array('&lt;?php', '&lt;?PHP', '&lt;?', '?&gt;'), $str);
adminb0dd10f2006-08-25 17:25:49 +0000447
448 /*
449 * Compact any exploded words
450 *
451 * This corrects words like: j a v a s c r i p t
452 * These words are compacted back to their correct state.
453 *
454 */
455 $words = array('javascript', 'vbscript', 'script', 'applet', 'alert', 'document', 'write', 'cookie', 'window');
456 foreach ($words as $word)
457 {
458 $temp = '';
459 for ($i = 0; $i < strlen($word); $i++)
460 {
461 $temp .= substr($word, $i, 1)."\s*";
462 }
463
464 $temp = substr($temp, 0, -3);
465 $str = preg_replace('#'.$temp.'#s', $word, $str);
466 $str = preg_replace('#'.ucfirst($temp).'#s', ucfirst($word), $str);
467 }
468
469 /*
470 * Remove disallowed Javascript in links or img tags
471 */
472 $str = preg_replace("#<a.+?href=.*?(alert\(|alert&\#40;|javascript\:|window\.|document\.|\.cookie|<script|<xss).*?\>.*?</a>#si", "", $str);
473 $str = preg_replace("#<img.+?src=.*?(alert\(|alert&\#40;|javascript\:|window\.|document\.|\.cookie|<script|<xss).*?\>#si", "", $str);
474 $str = preg_replace("#<(script|xss).*?\>#si", "", $str);
475
476 /*
477 * Remove JavaScript Event Handlers
478 *
479 * Note: This code is a little blunt. It removes
480 * the event handler and anything up to the closing >,
481 * but it's unlkely to be a problem.
482 *
483 */
484 $str = preg_replace('#(<[^>]+.*?)(onblur|onchange|onclick|onfocus|onload|onmouseover|onmouseup|onmousedown|onselect|onsubmit|onunload|onkeypress|onkeydown|onkeyup|onresize)[^>]*>#iU',"\\1>",$str);
485
486 /*
487 * Sanitize naughty HTML elements
488 *
489 * If a tag containing any of the words in the list
490 * below is found, the tag gets converted to entities.
491 *
492 * So this: <blink>
493 * Becomes: &lt;blink&gt;
494 *
495 */
496 $str = preg_replace('#<(/*\s*)(alert|applet|basefont|base|behavior|bgsound|blink|body|embed|expression|form|frameset|frame|head|html|ilayer|iframe|input|layer|link|meta|object|plaintext|style|script|textarea|title|xml|xss)([^>]*)>#is', "&lt;\\1\\2\\3&gt;", $str);
497
498 /*
499 * Sanitize naughty scripting elements
500 *
501 * Similar to above, only instead of looking for
502 * tags it looks for PHP and JavaScript commands
503 * that are disallowed. Rather than removing the
504 * code, it simply converts the parenthesis to entities
505 * rendering the code unexecutable.
506 *
507 * For example: eval('some code')
508 * Becomes: eval&#40;'some code'&#41;
509 *
510 */
511 $str = preg_replace('#(alert|cmd|passthru|eval|exec|system|fopen|fsockopen|file|file_get_contents|readfile|unlink)(\s*)\((.*?)\)#si', "\\1\\2&#40;\\3&#41;", $str);
512
513 /*
514 * Final clean up
515 *
516 * This adds a bit of extra precaution in case
517 * something got through the above filters
518 *
519 */
520 $bad = array(
521 'document.cookie' => '',
522 'document.write' => '',
523 'window.location' => '',
524 "javascript\s*:" => '',
525 "Redirect\s+302" => '',
526 '<!--' => '&lt;!--',
527 '-->' => '--&gt;'
528 );
529
530 foreach ($bad as $key => $val)
531 {
532 $str = preg_replace("#".$key."#i", $val, $str);
533 }
534
535
536 log_message('debug', "XSS Filtering completed");
537 return $str;
538 }
539 // END xss_clean()
540
541
542 /**
543 * HTML Entities Decode
544 *
545 * This function is a replacement for html_entity_decode()
546 *
547 * In some versions of PHP the native function does not work
548 * when UTF-8 is the specified character set, so this gives us
549 * a work-around. More info here:
550 * http://bugs.php.net/bug.php?id=25670
551 *
552 * @access private
553 * @param string
554 * @param string
555 * @return string
556 */
557 /* -------------------------------------------------
558 /* Replacement for html_entity_decode()
559 /* -------------------------------------------------*/
560
561 /*
562 NOTE: html_entity_decode() has a bug in some PHP versions when UTF-8 is the
563 character set, and the PHP developers said they were not back porting the
564 fix to versions other than PHP 5.x.
565 */
566 function _html_entity_decode($str, $charset='ISO-8859-1')
567 {
568 if (stristr($str, '&') === FALSE) return $str;
569
570 // The reason we are not using html_entity_decode() by itself is because
571 // while it is not technically correct to leave out the semicolon
572 // at the end of an entity most browsers will still interpret the entity
573 // correctly. html_entity_decode() does not convert entities without
574 // semicolons, so we are left with our own little solution here. Bummer.
575
576 if (function_exists('html_entity_decode') && (strtolower($charset) != 'utf-8' OR version_compare(phpversion(), '5.0.0', '>=')))
577 {
578 $str = html_entity_decode($str, ENT_COMPAT, $charset);
579 $str = preg_replace('~&#x([0-9a-f]{2,5})~ei', 'chr(hexdec("\\1"))', $str);
580 return preg_replace('~&#([0-9]{2,4})~e', 'chr(\\1)', $str);
581 }
582
583 // Numeric Entities
584 $str = preg_replace('~&#x([0-9a-f]{2,5});{0,1}~ei', 'chr(hexdec("\\1"))', $str);
585 $str = preg_replace('~&#([0-9]{2,4});{0,1}~e', 'chr(\\1)', $str);
586
587 // Literal Entities - Slightly slow so we do another check
588 if (stristr($str, '&') === FALSE)
589 {
590 $str = strtr($str, array_flip(get_html_translation_table(HTML_ENTITIES)));
591 }
592
593 return $str;
594 }
595
596}
597// END Input class
598?>