00001 <?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029 class CI_Input {
00030 var $use_xss_clean = FALSE;
00031 var $xss_hash = '';
00032 var $ip_address = FALSE;
00033 var $user_agent = FALSE;
00034 var $allow_get_array = FALSE;
00035
00036
00037 var $never_allowed_str = array(
00038 'document.cookie' => '[removed]',
00039 'document.write' => '[removed]',
00040 '.parentNode' => '[removed]',
00041 '.innerHTML' => '[removed]',
00042 'window.location' => '[removed]',
00043 '-moz-binding' => '[removed]',
00044 '<!--' => '<!--',
00045 '-->' => '-->',
00046 '<![CDATA[' => '<![CDATA['
00047 );
00048
00049 var $never_allowed_regex = array(
00050 "javascript\s*:" => '[removed]',
00051 "expression\s*\(" => '[removed]',
00052 "Redirect\s+302" => '[removed]'
00053 );
00054
00055
00056
00057
00058
00059
00060
00061
00062
00063 function CI_Input()
00064 {
00065 log_message('debug', "Input Class Initialized");
00066
00067 $CFG =& load_class('Config');
00068 $this->use_xss_clean = ($CFG->item('global_xss_filtering') === TRUE) ? TRUE : FALSE;
00069 $this->allow_get_array = ($CFG->item('enable_query_strings') === TRUE) ? TRUE : FALSE;
00070 $this->_sanitize_globals();
00071 }
00072
00073
00074
00075
00076
00077
00078
00079
00080
00081
00082
00083
00084
00085
00086
00087
00088
00089 function _sanitize_globals()
00090 {
00091
00092 $protected = array('_SERVER', '_GET', '_POST', '_FILES', '_REQUEST', '_SESSION', '_ENV', 'GLOBALS', 'HTTP_RAW_POST_DATA',
00093 'system_folder', 'application_folder', 'BM', 'EXT', 'CFG', 'URI', 'RTR', 'OUT', 'IN');
00094
00095
00096
00097 foreach (array($_GET, $_POST, $_COOKIE, $_SERVER, $_FILES, $_ENV, (isset($_SESSION) && is_array($_SESSION)) ? $_SESSION : array()) as $global)
00098 {
00099 if ( ! is_array($global))
00100 {
00101 if ( ! in_array($global, $protected))
00102 {
00103 unset($GLOBALS[$global]);
00104 }
00105 }
00106 else
00107 {
00108 foreach ($global as $key => $val)
00109 {
00110 if ( ! in_array($key, $protected))
00111 {
00112 unset($GLOBALS[$key]);
00113 }
00114
00115 if (is_array($val))
00116 {
00117 foreach($val as $k => $v)
00118 {
00119 if ( ! in_array($k, $protected))
00120 {
00121 unset($GLOBALS[$k]);
00122 }
00123 }
00124 }
00125 }
00126 }
00127 }
00128
00129
00130 if ($this->allow_get_array == FALSE)
00131 {
00132 $_GET = array();
00133 }
00134 else
00135 {
00136 $_GET = $this->_clean_input_data($_GET);
00137 }
00138
00139
00140 $_POST = $this->_clean_input_data($_POST);
00141
00142
00143 $_COOKIE = $this->_clean_input_data($_COOKIE);
00144
00145 log_message('debug', "Global POST and COOKIE data sanitized");
00146 }
00147
00148
00149
00150
00151
00152
00153
00154
00155
00156
00157
00158
00159
00160 function _clean_input_data($str)
00161 {
00162 if (is_array($str))
00163 {
00164 $new_array = array();
00165 foreach ($str as $key => $val)
00166 {
00167 $new_array[$this->_clean_input_keys($key)] = $this->_clean_input_data($val);
00168 }
00169 return $new_array;
00170 }
00171
00172
00173 if (get_magic_quotes_gpc())
00174 {
00175 $str = stripslashes($str);
00176 }
00177
00178
00179 if ($this->use_xss_clean === TRUE)
00180 {
00181 $str = $this->xss_clean($str);
00182 }
00183
00184
00185 if (strpos($str, "\r") !== FALSE)
00186 {
00187 $str = str_replace(array("\r\n", "\r"), "\n", $str);
00188 }
00189
00190 return $str;
00191 }
00192
00193
00194
00195
00196
00197
00198
00199
00200
00201
00202
00203
00204
00205
00206 function _clean_input_keys($str)
00207 {
00208 if ( ! preg_match("/^[a-z0-9:_\/-]+$/i", $str))
00209 {
00210 exit('Disallowed Key Characters.');
00211 }
00212
00213 return $str;
00214 }
00215
00216
00217
00218
00219
00220
00221
00222
00223
00224
00225
00226
00227
00228
00229 function _fetch_from_array(&$array, $index = '', $xss_clean = FALSE)
00230 {
00231 if ( ! isset($array[$index]))
00232 {
00233 return FALSE;
00234 }
00235
00236 if ($xss_clean === TRUE)
00237 {
00238 return $this->xss_clean($array[$index]);
00239 }
00240
00241 return $array[$index];
00242 }
00243
00244
00245
00246
00247
00248
00249
00250
00251
00252
00253
00254 function get($index = '', $xss_clean = FALSE)
00255 {
00256 return $this->_fetch_from_array($_GET, $index, $xss_clean);
00257 }
00258
00259
00260
00261
00262
00263
00264
00265
00266
00267
00268
00269 function post($index = '', $xss_clean = FALSE)
00270 {
00271 return $this->_fetch_from_array($_POST, $index, $xss_clean);
00272 }
00273
00274
00275
00276
00277
00278
00279
00280
00281
00282
00283
00284 function get_post($index = '', $xss_clean = FALSE)
00285 {
00286 if ( ! isset($_POST[$index]) )
00287 {
00288 return $this->get($index, $xss_clean);
00289 }
00290 else
00291 {
00292 return $this->post($index, $xss_clean);
00293 }
00294 }
00295
00296
00297
00298
00299
00300
00301
00302
00303
00304
00305
00306 function cookie($index = '', $xss_clean = FALSE)
00307 {
00308 return $this->_fetch_from_array($_COOKIE, $index, $xss_clean);
00309 }
00310
00311
00312
00313
00314
00315
00316
00317
00318
00319
00320
00321 function server($index = '', $xss_clean = FALSE)
00322 {
00323 return $this->_fetch_from_array($_SERVER, $index, $xss_clean);
00324 }
00325
00326
00327
00328
00329
00330
00331
00332
00333
00334 function ip_address()
00335 {
00336 if ($this->ip_address !== FALSE)
00337 {
00338 return $this->ip_address;
00339 }
00340
00341 if ($this->server('REMOTE_ADDR') AND $this->server('HTTP_CLIENT_IP'))
00342 {
00343 $this->ip_address = $_SERVER['HTTP_CLIENT_IP'];
00344 }
00345 elseif ($this->server('REMOTE_ADDR'))
00346 {
00347 $this->ip_address = $_SERVER['REMOTE_ADDR'];
00348 }
00349 elseif ($this->server('HTTP_CLIENT_IP'))
00350 {
00351 $this->ip_address = $_SERVER['HTTP_CLIENT_IP'];
00352 }
00353 elseif ($this->server('HTTP_X_FORWARDED_FOR'))
00354 {
00355 $this->ip_address = $_SERVER['HTTP_X_FORWARDED_FOR'];
00356 }
00357
00358 if ($this->ip_address === FALSE)
00359 {
00360 $this->ip_address = '0.0.0.0';
00361 return $this->ip_address;
00362 }
00363
00364 if (strstr($this->ip_address, ','))
00365 {
00366 $x = explode(',', $this->ip_address);
00367 $this->ip_address = end($x);
00368 }
00369
00370 if ( ! $this->valid_ip($this->ip_address))
00371 {
00372 $this->ip_address = '0.0.0.0';
00373 }
00374
00375 return $this->ip_address;
00376 }
00377
00378
00379
00380
00381
00382
00383
00384
00385
00386
00387
00388
00389 function valid_ip($ip)
00390 {
00391 $ip_segments = explode('.', $ip);
00392
00393
00394 if (count($ip_segments) != 4)
00395 {
00396 return FALSE;
00397 }
00398
00399 if (substr($ip_segments[0], 0, 1) == '0')
00400 {
00401 return FALSE;
00402 }
00403
00404 foreach ($ip_segments as $segment)
00405 {
00406
00407
00408 if (preg_match("/[^0-9]/", $segment) OR $segment > 255 OR strlen($segment) > 3)
00409 {
00410 return FALSE;
00411 }
00412 }
00413
00414 return TRUE;
00415 }
00416
00417
00418
00419
00420
00421
00422
00423
00424
00425 function user_agent()
00426 {
00427 if ($this->user_agent !== FALSE)
00428 {
00429 return $this->user_agent;
00430 }
00431
00432 $this->user_agent = ( ! isset($_SERVER['HTTP_USER_AGENT'])) ? FALSE : $_SERVER['HTTP_USER_AGENT'];
00433
00434 return $this->user_agent;
00435 }
00436
00437
00438
00439
00440
00441
00442
00443
00444
00445
00446 function filename_security($str)
00447 {
00448 $bad = array(
00449 "../",
00450 "./",
00451 "<!--",
00452 "-->",
00453 "<",
00454 ">",
00455 "'",
00456 '"',
00457 '&',
00458 '$',
00459 '#',
00460 '{',
00461 '}',
00462 '[',
00463 ']',
00464 '=',
00465 ';',
00466 '?',
00467 "%20",
00468 "%22",
00469 "%3c",
00470 "%253c",
00471 "%3e",
00472 "%0e",
00473 "%28",
00474 "%29",
00475 "%2528",
00476 "%26",
00477 "%24",
00478 "%3f",
00479 "%3b",
00480 "%3d"
00481 );
00482
00483 return stripslashes(str_replace($bad, '', $str));
00484 }
00485
00486
00487
00488
00489
00490
00491
00492
00493
00494
00495
00496
00497
00498
00499
00500
00501
00502
00503
00504
00505
00506
00507
00508
00509
00510
00511
00512
00513
00514 function xss_clean($str, $is_image = FALSE)
00515 {
00516
00517
00518
00519
00520 if (is_array($str))
00521 {
00522 while (list($key) = each($str))
00523 {
00524 $str[$key] = $this->xss_clean($str[$key]);
00525 }
00526
00527 return $str;
00528 }
00529
00530
00531
00532
00533 $str = $this->_remove_invisible_characters($str);
00534
00535
00536
00537
00538
00539
00540
00541 $str = preg_replace('|\&([a-z\_0-9]+)\=([a-z\_0-9]+)|i', $this->xss_hash()."\\1=\\2", $str);
00542
00543
00544
00545
00546
00547
00548
00549
00550 $str = preg_replace('#(&\#?[0-9a-z]+)[\x00-\x20]*;?#i', "\\1;", $str);
00551
00552
00553
00554
00555
00556
00557
00558 $str = preg_replace('#(&\#x?)([0-9A-F]+);?#i',"\\1\\2;",$str);
00559
00560
00561
00562
00563 $str = str_replace($this->xss_hash(), '&', $str);
00564
00565
00566
00567
00568
00569
00570
00571
00572
00573
00574
00575 $str = rawurldecode($str);
00576
00577
00578
00579
00580
00581
00582
00583
00584
00585
00586 $str = preg_replace_callback("/[a-z]+=([\'\"]).*?\\1/si", array($this, '_convert_attribute'), $str);
00587
00588 $str = preg_replace_callback("/<\w+.*?(?=>|<|$)/si", array($this, '_html_entity_decode_callback'), $str);
00589
00590
00591
00592
00593 $str = $this->_remove_invisible_characters($str);
00594
00595
00596
00597
00598
00599
00600
00601
00602
00603
00604
00605 if (strpos($str, "\t") !== FALSE)
00606 {
00607 $str = str_replace("\t", ' ', $str);
00608 }
00609
00610
00611
00612
00613 $converted_string = $str;
00614
00615
00616
00617
00618
00619 foreach ($this->never_allowed_str as $key => $val)
00620 {
00621 $str = str_replace($key, $val, $str);
00622 }
00623
00624 foreach ($this->never_allowed_regex as $key => $val)
00625 {
00626 $str = preg_replace("#".$key."#i", $val, $str);
00627 }
00628
00629
00630
00631
00632
00633
00634
00635
00636
00637
00638
00639 if ($is_image === TRUE)
00640 {
00641
00642
00643 $str = str_replace(array('<?php', '<?PHP'), array('<?php', '<?PHP'), $str);
00644 }
00645 else
00646 {
00647 $str = str_replace(array('<?php', '<?PHP', '<?', '?'.'>'), array('<?php', '<?PHP', '<?', '?>'), $str);
00648 }
00649
00650
00651
00652
00653
00654
00655
00656
00657 $words = array('javascript', 'expression', 'vbscript', 'script', 'applet', 'alert', 'document', 'write', 'cookie', 'window');
00658 foreach ($words as $word)
00659 {
00660 $temp = '';
00661
00662 for ($i = 0, $wordlen = strlen($word); $i < $wordlen; $i++)
00663 {
00664 $temp .= substr($word, $i, 1)."\s*";
00665 }
00666
00667
00668
00669 $str = preg_replace_callback('#('.substr($temp, 0, -3).')(\W)#is', array($this, '_compact_exploded_words'), $str);
00670 }
00671
00672
00673
00674
00675
00676
00677 do
00678 {
00679 $original = $str;
00680
00681 if (preg_match("/<a/i", $str))
00682 {
00683 $str = preg_replace_callback("#<a\s*([^>]*?)(>|$)#si", array($this, '_js_link_removal'), $str);
00684 }
00685
00686 if (preg_match("/<img/i", $str))
00687 {
00688 $str = preg_replace_callback("#<img\s*([^>]*?)(>|$)#si", array($this, '_js_img_removal'), $str);
00689 }
00690
00691 if (preg_match("/script/i", $str) OR preg_match("/xss/i", $str))
00692 {
00693 $str = preg_replace("#<(/*)(script|xss)(.*?)>#si", '[removed]', $str);
00694 }
00695 }
00696 while($original != $str);
00697
00698 unset($original);
00699
00700
00701
00702
00703
00704
00705
00706
00707
00708 $event_handlers = array('on\w*','xmlns');
00709
00710 if ($is_image === TRUE)
00711 {
00712
00713
00714
00715
00716 unset($event_handlers[array_search('xmlns', $event_handlers)]);
00717 }
00718
00719 $str = preg_replace("#<([^><]+)(".implode('|', $event_handlers).")(\s*=\s*[^><]*)([><]*)#i", "<\\1\\4", $str);
00720
00721
00722
00723
00724
00725
00726
00727
00728
00729
00730
00731 $naughty = 'alert|applet|audio|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|video|xml|xss';
00732 $str = preg_replace_callback('#<(/*\s*)('.$naughty.')([^><]*)([><]*)#is', array($this, '_sanitize_naughty_html'), $str);
00733
00734
00735
00736
00737
00738
00739
00740
00741
00742
00743
00744
00745
00746
00747 $str = preg_replace('#(alert|cmd|passthru|eval|exec|expression|system|fopen|fsockopen|file|file_get_contents|readfile|unlink)(\s*)\((.*?)\)#si', "\\1\\2(\\3)", $str);
00748
00749
00750
00751
00752
00753
00754
00755
00756 foreach ($this->never_allowed_str as $key => $val)
00757 {
00758 $str = str_replace($key, $val, $str);
00759 }
00760
00761 foreach ($this->never_allowed_regex as $key => $val)
00762 {
00763 $str = preg_replace("#".$key."#i", $val, $str);
00764 }
00765
00766
00767
00768
00769
00770
00771
00772
00773
00774 if ($is_image === TRUE)
00775 {
00776 if ($str == $converted_string)
00777 {
00778 return TRUE;
00779 }
00780 else
00781 {
00782 return FALSE;
00783 }
00784 }
00785
00786 log_message('debug', "XSS Filtering completed");
00787 return $str;
00788 }
00789
00790
00791
00792
00793
00794
00795
00796
00797
00798 function xss_hash()
00799 {
00800 if ($this->xss_hash == '')
00801 {
00802 if (phpversion() >= 4.2)
00803 mt_srand();
00804 else
00805 mt_srand(hexdec(substr(md5(microtime()), -8)) & 0x7fffffff);
00806
00807 $this->xss_hash = md5(time() + mt_rand(0, 1999999999));
00808 }
00809
00810 return $this->xss_hash;
00811 }
00812
00813
00814
00815
00816
00817
00818
00819
00820
00821
00822
00823
00824
00825 function _remove_invisible_characters($str)
00826 {
00827 static $non_displayables;
00828
00829 if ( ! isset($non_displayables))
00830 {
00831
00832
00833 $non_displayables = array(
00834 '/%0[0-8]/', '/[\x00-\x08]/',
00835 '/%11/', '/\x0b/', '/%12/', '/\x0c/',
00836 '/%1[4-9]/', '/%2[0-9]/', '/%3[0-1]/',
00837 '/[\x0e-\x1f]/');
00838
00839 }
00840
00841 do
00842 {
00843 $cleaned = $str;
00844 $str = preg_replace($non_displayables, '', $str);
00845 }
00846 while ($cleaned != $str);
00847
00848 return $str;
00849 }
00850
00851
00852
00853
00854
00855
00856
00857
00858
00859
00860
00861
00862
00863 function _compact_exploded_words($matches)
00864 {
00865 return preg_replace('/\s+/s', '', $matches[1]).$matches[2];
00866 }
00867
00868
00869
00870
00871
00872
00873
00874
00875
00876
00877
00878
00879 function _sanitize_naughty_html($matches)
00880 {
00881
00882 $str = '<'.$matches[1].$matches[2].$matches[3];
00883
00884
00885 $str .= str_replace(array('>', '<'), array('>', '<'), $matches[4]);
00886
00887 return $str;
00888 }
00889
00890
00891
00892
00893
00894
00895
00896
00897
00898
00899
00900
00901
00902
00903
00904 function _js_link_removal($match)
00905 {
00906 $attributes = $this->_filter_attributes(str_replace(array('<', '>'), '', $match[1]));
00907 return str_replace($match[1], preg_replace("#href=.*?(alert\(|alert&\#40;|javascript\:|charset\=|window\.|document\.|\.cookie|<script|<xss|base64\s*,)#si", "", $attributes), $match[0]);
00908 }
00909
00910
00911
00912
00913
00914
00915
00916
00917
00918
00919
00920
00921
00922 function _js_img_removal($match)
00923 {
00924 $attributes = $this->_filter_attributes(str_replace(array('<', '>'), '', $match[1]));
00925 return str_replace($match[1], preg_replace("#src=.*?(alert\(|alert&\#40;|javascript\:|charset\=|window\.|document\.|\.cookie|<script|<xss|base64\s*,)#si", "", $attributes), $match[0]);
00926 }
00927
00928
00929
00930
00931
00932
00933
00934
00935
00936
00937
00938
00939 function _convert_attribute($match)
00940 {
00941 return str_replace(array('>', '<'), array('>', '<'), $match[0]);
00942 }
00943
00944
00945
00946
00947
00948
00949
00950
00951
00952
00953
00954
00955 function _html_entity_decode_callback($match)
00956 {
00957 global $CFG;
00958 $charset = $CFG->item('charset');
00959
00960 return $this->_html_entity_decode($match[0], strtoupper($charset));
00961 }
00962
00963
00964
00965
00966
00967
00968
00969
00970
00971
00972
00973
00974
00975
00976
00977
00978
00979
00980
00981
00982
00983
00984
00985
00986
00987
00988
00989 function _html_entity_decode($str, $charset='UTF-8')
00990 {
00991 if (stristr($str, '&') === FALSE) return $str;
00992
00993
00994
00995
00996
00997
00998
00999 if (function_exists('html_entity_decode') && (strtolower($charset) != 'utf-8' OR version_compare(phpversion(), '5.0.0', '>=')))
01000 {
01001 $str = html_entity_decode($str, ENT_COMPAT, $charset);
01002 $str = preg_replace('~&#x(0*[0-9a-f]{2,5})~ei', 'chr(hexdec("\\1"))', $str);
01003 return preg_replace('~&#([0-9]{2,4})~e', 'chr(\\1)', $str);
01004 }
01005
01006
01007 $str = preg_replace('~&#x(0*[0-9a-f]{2,5});{0,1}~ei', 'chr(hexdec("\\1"))', $str);
01008 $str = preg_replace('~&#([0-9]{2,4});{0,1}~e', 'chr(\\1)', $str);
01009
01010
01011 if (stristr($str, '&') === FALSE)
01012 {
01013 $str = strtr($str, array_flip(get_html_translation_table(HTML_ENTITIES)));
01014 }
01015
01016 return $str;
01017 }
01018
01019
01020
01021
01022
01023
01024
01025
01026
01027
01028
01029
01030 function _filter_attributes($str)
01031 {
01032 $out = '';
01033
01034 if (preg_match_all('#\s*[a-z\-]+\s*=\s*(\042|\047)([^\\1]*?)\\1#is', $str, $matches))
01035 {
01036 foreach ($matches[0] as $match)
01037 {
01038 $out .= "{$match}";
01039 }
01040 }
01041
01042 return $out;
01043 }
01044
01045
01046
01047 }
01048
01049
01050
01051