[ Index ] |
|
Code source de PHPonTrax 2.6.6-svn |
1 <?php 2 /** 3 * File containing the InputFilter class 4 * 5 * (PHP 5) 6 * 7 * @package PHPonTrax 8 * @version $Id: input_filter.php 245 2006-08-23 06:15:06Z john $ 9 * @author Daniel Morris 10 * contributors: Gianpaolo Racca, Ghislain Picard, Marco Wandschneider, 11 * Chris Tobin and Andrew Eddie. 12 * @copyright Daniel Morris <dan@rootcube.com> 13 * @license http://opensource.org/licenses/gpl-license.php GNU Public License 14 */ 15 16 /** 17 * Filter user input to remove potential security threats 18 * 19 * InputFilter has three public methods that are useful in protecting 20 * a web site from potential security threats from user input. 21 * <ul> 22 * <li>{@link safeSQL()} protects SQL from the user.</li> 23 * <li>{@link process()} protects HTML tags and attributes from the 24 * user.</li> 25 * <li>{@link process_all()} applies {@link process()} to all 26 * possible sources of user input</li> 27 * </ul> 28 * For usage instructions see 29 * {@tutorial PHPonTrax/InputFilter.cls the class tutorial}. 30 * @todo Check FIXMEs 31 */ 32 class InputFilter { 33 34 /** 35 * User-provided list of tags to either accept or reject 36 * 37 * Whether the tags in this list are accepted or rejected is 38 * determined by the value of {@link $tagsMethod}. 39 * @var string[] 40 */ 41 protected static $tagsArray = array(); // default = empty array 42 43 /** 44 * User-provided list of attributes to either accept or reject 45 * 46 * Whether the attributes in this list are accepted or rejected is 47 * determined by the value of {@link $attrMethod}. 48 * @var string[] 49 */ 50 protected static $attrArray = array(); // default = empty array 51 52 /** 53 * How to apply user-provided tags list 54 * 55 * Which method to use when applying the list of tags provided by 56 * the user and stored in {@link $tagsArray}. 57 * @var boolean Tested by {@link filterTags()} to see whether the 58 * user-provide list of tags in {@link $tagsArray} 59 * describes those tags which are forbidden, or 60 * those tags which are permitted. Default false. 61 * <ul> 62 * <li>true => Remove those tags which are in 63 * {@link $tagsArray}.</li> 64 * <li>false => Allow only those tags which are listed in 65 * {@link $tagsArray}.</li> 66 * </ul> 67 */ 68 protected static $tagsMethod = true; 69 70 /** 71 * How to apply user-provided attribute list 72 * 73 * Which method to use when applying the list of attributes 74 * provided by the user and stored in {@link $attrArray}. 75 * @var boolean Tested by {@link filterAttr()} to see whether the 76 * user-provide list of tags in {@link $attrArray} 77 * describes those tags which are forbidden, or 78 * those tags which are permitted. Default false. 79 * <ul> 80 * <li>true => Remove those tags which are in 81 * {@link $attrArray}.</li> 82 * <li>false => Allow only those tags which are listed in 83 * {@link $attrArray}.</li> 84 * </ul> 85 */ 86 protected static $attrMethod = true; 87 88 89 /** 90 * Whether to remove blacklisted tags and attributes 91 * 92 * @var boolean Tested by {@link filterAttr()} and 93 * {@link filterTags()} to see whether to remove 94 * blacklisted tags and attributes. Default true. 95 * <ul> 96 * <li>true => Remove tags in {@link $tagBlacklist} and 97 * attributes in {@link $attrBlacklist}, in 98 * addition to all other potentially suspect tags 99 * and attributes.</li> 100 * <li>false => Remove potentially suspect tags and attributes 101 * without consulting{@link $tagBlacklist} or 102 * {@link $attrBlacklist}.</li> 103 * </ul> 104 */ 105 protected static $xssAuto = true; 106 107 /** 108 * Fields to ignore that you want html and other banned stuff in. 109 * 110 * @var array 111 */ 112 protected static $exception_fields = array(); 113 114 /** 115 * List of tags to be removed 116 * 117 * If {@link $xssAuto} is true, remove the tags in this list. 118 * @var string[] 119 */ 120 protected static $tagBlacklist = 121 array('applet', 'body', 'bgsound', 'base', 'basefont', 'embed', 122 'frame', 'frameset', 'head', 'html', 'id', 'iframe', 123 'ilayer', 'layer', 'link', 'meta', 'name', 'object', 124 'script', 'style', 'title', 'xml'); 125 126 /** 127 * List of attributes to be removed 128 * 129 * If {@link $xssAuto} is true, remove the attributes in this list. 130 * @var string[] 131 */ 132 protected static $attrBlacklist = 133 array('action', 'background', 'codebase', 'dynsrc', 'lowsrc'); 134 135 /** 136 * Initializer for InputFilter class. 137 * 138 * @param string[] $tagsArray User-provided list of tags to 139 * either accept or reject. Default: none 140 * @param string[] $attrArray User-provided list of attributes to 141 * either accept or reject. Default: none 142 * @param boolean $tagsMethod How to apply the list of tags in $tagsArray: 143 * <ul> 144 * <li>true => Remove those tags which are listed in 145 * $tagsArray.</li> 146 * <li>false => Allow only those tags which are listed in 147 * $tagsArray.</li> 148 * </ul> 149 * Default: false 150 * @param boolean $attrMethod How to apply the list of attributess in $attrArray: 151 * <ul> 152 * <li>true => Remove those attributes which are listed in 153 * $attrArray.</li> 154 * <li>false => Allow only those attributes which are listed in 155 * $attrArray.</li> 156 * </ul> 157 * Default: false 158 * @param boolean $xssAuto Behavior of {@link filterTags()}: 159 * <ul> 160 * <li>true => Remove tags in {@link $tagBlacklist} and 161 * attributes in {@link $attrBlacklist}, in 162 * addition to all other potentially suspect tags 163 * and attributes.</li> 164 * <li>false => Remove potentially suspect tags and attributes 165 * without consulting{@link $tagBlacklist} or 166 * {@link $attrBlacklist}.</li> 167 * </ul> 168 * Default: true 169 * @uses $attrArray 170 * @uses $attrMethod 171 * @uses $tagsArray 172 * @uses $tagsMethod 173 */ 174 public function init($tagsArray = array(), $attrArray = array(), 175 $tagsMethod = true, $attrMethod = true, 176 $xssAuto = true) { 177 178 // make sure user defined arrays are in lowercase 179 for ($i = 0; $i < count($tagsArray); $i++) $tagsArray[$i] = strtolower($tagsArray[$i]); 180 for ($i = 0; $i < count($attrArray); $i++) $attrArray[$i] = strtolower($attrArray[$i]); 181 // assign to member vars 182 self::$tagsArray = (array) $tagsArray; 183 self::$attrArray = (array) $attrArray; 184 self::$tagsMethod = $tagsMethod; 185 self::$attrMethod = $attrMethod; 186 self::$xssAuto = $xssAuto; 187 } 188 189 /** 190 * Adds a field to exclude from filtering 191 * 192 */ 193 public function add_field_exception($field) { 194 if($field) { 195 self::$exception_fields[] = $field; 196 } 197 } 198 199 /** 200 * Clears all previous field exceptions 201 * 202 */ 203 public function clear_field_exceptions() { 204 self::$exception_fields = array(); 205 } 206 207 /** 208 * Remove forbidden tags and attributes from user input 209 * 210 * Construct an InputFilter object. Then apply the 211 * {@link process()} method to each of the user input arrays 212 * {@link http://www.php.net/reserved.variables#reserved.variables.post $_POST}, 213 * {@link http://www.php.net/reserved.variables#reserved.variables.get $_GET} and 214 * {@link http://www.php.net/reserved.variables#reserved.variables.request $_REQUEST}. 215 * <b>FIXME:</b> isn't it partly redundant to do this to $_REQUEST? 216 * Shouldn't we do it to $_COOKIE instead? 217 * @param string[] $tagsArray User-provided list of tags to 218 * either accept or reject. Default: none 219 * @param string[] $attrArray User-provided list of attributes to 220 * either accept or reject. Default: none 221 * @param boolean $tagsMethod How to apply the list of tags in $tagsArray: 222 * <ul> 223 * <li>true => Remove those tags which are listed in 224 * $tagsArray.</li> 225 * <li>false => Allow only those tags which are listed in 226 * $tagsArray.</li> 227 * </ul> 228 * Default: false 229 * @param boolean $attrMethod How to apply the list of attributess in $attrArray: 230 * <ul> 231 * <li>true => Remove those attributes which are listed in 232 * $attrArray.</li> 233 * <li>false => Allow only those attributes which are listed in 234 * $attrArray.</li> 235 * </ul> 236 * Default: false 237 * @param boolean $xssAuto Behavior of {@link filterTags()}: 238 * <ul> 239 * <li>true => Remove tags in {@link $tagBlacklist} and 240 * attributes in {@link $attrBlacklist}, in 241 * addition to all other potentially suspect tags 242 * and attributes.</li> 243 * <li>false => Remove potentially suspect tags and attributes 244 * without consulting{@link $tagBlacklist} or 245 * {@link $attrBlacklist}.</li> 246 * </ul> 247 * Default: true 248 * @author John Peterson 249 * @uses __construct() 250 * @uses process() 251 * @todo Check out FIXMEs 252 */ 253 public function process_all($tagsArray = array(), $attrArray = array(), 254 $tagsMethod = true, $attrMethod = true, 255 $xssAuto = true) { 256 self::init($tagsArray, $attrArray, $tagsMethod, 257 $attrMethod, $xssAuto); 258 if(count($_POST)) { 259 $_POST = self::process($_POST); 260 } 261 if(count($_GET)) { 262 $_GET = self::process($_GET); 263 } 264 if(count($_REQUEST)) { 265 $_REQUEST = self::process($_REQUEST); 266 } 267 } 268 269 /** 270 * Remove forbidden tags and attributes from array of strings 271 * 272 * Accept a string or array of strings. For each string in the 273 * source, remove the forbidden tags and attributes from the string. 274 * @param mixed $source - input string/array-of-string to be 'cleaned' 275 * @return mixed 'cleaned' version of input parameter 276 * @uses decode() 277 * @uses remove() 278 */ 279 public function process($source, $extra_key = null) { 280 // clean all elements in this array 281 if(is_array($source)) { 282 foreach($source as $key => $value) { 283 //error_log("key:".$extra_key.$key); 284 if(in_array($extra_key.$key, self::$exception_fields)) { $source[$key] = $value; continue; } 285 // for arrays in arrays 286 if (is_array($value)) $source[$key] = self::process($value, $key.":"); 287 // filter element for XSS and other 'bad' code etc. 288 if (is_string($value)) $source[$key] = self::remove(self::decode($value)); 289 } 290 return $source; 291 // clean this string 292 } elseif(is_string($source)) { 293 // filter source for XSS and other 'bad' code etc. 294 return self::remove(self::decode($source)); 295 // return parameter as given 296 } else { 297 return $source; 298 } 299 } 300 301 /** 302 * Remove forbidden tags and attributes from a string iteratively 303 * 304 * Call {@link filterTags()} repeatedly until no change in the 305 * input is produced. 306 * @param string $source Input string to be 'cleaned' 307 * @return string 'cleaned' version of $source 308 * @uses filterTags() 309 */ 310 protected function remove($source) { 311 // provides nested-tag protection 312 while($source != self::filterTags($source)) { 313 $source = self::filterTags($source); 314 } 315 return $source; 316 } 317 318 /** 319 * Remove forbidden tags and attributes from a string 320 * 321 * Inspect the input for tags "<tagname ...>" and check the tag 322 * name against a list of forbidden tag names. Delete all tags 323 * with forbidden names. If {@link $xssAuto} is true, delete all 324 * tags in {@link $tagBlacklist}. If there is a user-defined tag 325 * list in {@link $tagsArray}, process according to the value of 326 * {@link $tagsMethod}. 327 * 328 * If the tag name is OK, then call {@link filterAttr()} to check 329 * all attributes of the tag and delete forbidden attributes. 330 * @param string $source Input string to be 'cleaned' 331 * @return string Cleaned version of input parameter 332 * @uses filterAttr() 333 * @uses $tagBlacklist 334 * @uses $tagsArray 335 * @uses $tagsMethod 336 * @uses $xssAuto 337 */ 338 protected function filterTags($source) { 339 // filter pass setup 340 $preTag = null; 341 $postTag = $source; 342 // find initial tag's position 343 $tagOpen_start = strpos($source, '<'); 344 // interate through string until no tags left 345 while($tagOpen_start !== false) { 346 // process tag interatively 347 $preTag .= substr($postTag, 0, $tagOpen_start); 348 $postTag = substr($postTag, $tagOpen_start); 349 $fromTagOpen = substr($postTag, 1); 350 // end of tag 351 $tagOpen_end = strpos($fromTagOpen, '>'); 352 if ($tagOpen_end === false) break; 353 // next start of tag (for nested tag assessment) 354 $tagOpen_nested = strpos($fromTagOpen, '<'); 355 if (($tagOpen_nested !== false) && ($tagOpen_nested < $tagOpen_end)) { 356 $preTag .= substr($postTag, 0, ($tagOpen_nested+1)); 357 $postTag = substr($postTag, ($tagOpen_nested+1)); 358 $tagOpen_start = strpos($postTag, '<'); 359 continue; 360 } 361 $tagOpen_nested = (strpos($fromTagOpen, '<') + $tagOpen_start + 1); 362 $currentTag = substr($fromTagOpen, 0, $tagOpen_end); 363 $tagLength = strlen($currentTag); 364 if (!$tagOpen_end) { 365 $preTag .= $postTag; 366 $tagOpen_start = strpos($postTag, '<'); 367 } 368 // iterate through tag finding attribute pairs - setup 369 $tagLeft = $currentTag; 370 $attrSet = array(); 371 $currentSpace = strpos($tagLeft, ' '); 372 // is end tag 373 if (substr($currentTag, 0, 1) == "/") { 374 $isCloseTag = true; 375 list($tagName) = explode(' ', $currentTag); 376 $tagName = substr($tagName, 1); 377 // is start tag 378 } else { 379 $isCloseTag = false; 380 list($tagName) = explode(' ', $currentTag); 381 } 382 // excludes all "non-regular" tagnames OR no tagname OR remove if xssauto is on and tag is blacklisted 383 if ((!preg_match("/^[a-z][a-z0-9]*$/i",$tagName)) || (!$tagName) || ((in_array(strtolower($tagName), self::$tagBlacklist)) && (self::$xssAuto))) { 384 $postTag = substr($postTag, ($tagLength + 2)); 385 $tagOpen_start = strpos($postTag, '<'); 386 // don't append this tag 387 continue; 388 } 389 // this while is needed to support attribute values with spaces in! 390 while ($currentSpace !== false) { 391 $fromSpace = substr($tagLeft, ($currentSpace+1)); 392 $nextSpace = strpos($fromSpace, ' '); 393 $openQuotes = strpos($fromSpace, '"'); 394 $closeQuotes = strpos(substr($fromSpace, ($openQuotes+1)), '"') + $openQuotes + 1; 395 // another equals exists 396 if (strpos($fromSpace, '=') !== false) { 397 // opening and closing quotes exists 398 if (($openQuotes !== false) && (strpos(substr($fromSpace, ($openQuotes+1)), '"') !== false)) 399 $attr = substr($fromSpace, 0, ($closeQuotes+1)); 400 // one or neither exist 401 else $attr = substr($fromSpace, 0, $nextSpace); 402 // no more equals exist 403 } else $attr = substr($fromSpace, 0, $nextSpace); 404 // last attr pair 405 if (!$attr) $attr = $fromSpace; 406 // add to attribute pairs array 407 $attrSet[] = $attr; 408 // next inc 409 $tagLeft = substr($fromSpace, strlen($attr)); 410 $currentSpace = strpos($tagLeft, ' '); 411 } 412 // appears in array specified by user 413 $tagFound = in_array(strtolower($tagName), self::$tagsArray); 414 // remove this tag on condition 415 if ((!$tagFound && self::$tagsMethod) || ($tagFound && !self::$tagsMethod)) { 416 // reconstruct tag with allowed attributes 417 if (!$isCloseTag) { 418 $attrSet = self::filterAttr($attrSet); 419 $preTag .= '<' . $tagName; 420 for ($i = 0; $i < count($attrSet); $i++) 421 $preTag .= ' ' . $attrSet[$i]; 422 // reformat single tags to XHTML 423 if (strpos($fromTagOpen, "</" . $tagName)) $preTag .= '>'; 424 else $preTag .= ' />'; 425 // just the tagname 426 } else $preTag .= '</' . $tagName . '>'; 427 } 428 // find next tag's start 429 $postTag = substr($postTag, ($tagLength + 2)); 430 $tagOpen_start = strpos($postTag, '<'); 431 } 432 // append any code after end of tags 433 $preTag .= $postTag; 434 return $preTag; 435 } 436 437 /** 438 * Internal method to strip a tag of certain attributes 439 * 440 * Remove potentially dangerous attributes from a set of 441 * "attr=value" strings. Attributes considered dangerous are: 442 * <ul> 443 * <li>Any attribute name containing any non-alphabetic 444 * character</li> 445 * <li>Any attribute name beginning "on..."</li> 446 * <li>If {@link $xssAuto} is true, any attribute name in 447 * {@link $attrBlacklist}</li> 448 * <li>Any attribute with a value containing the strings 449 * 'javascript:', 'behaviour:', 'vbscript:', 'mocha:', 450 * 'livescript:'</li> 451 * <li>Any attribute whose name contains 'style' and whose 452 * value contains 'expression'.</li> 453 * <li>If there is a user-provided list of attributes in 454 * {@link $attrArray}, process according to the value of 455 * {@link $attrMethod}.</li> 456 * </ul> 457 * @param string[] $attrSet Array of strings "attr=value" parsed 458 * from a tag. 459 * @return string[] Input with potentially dangerous attributes 460 * removed 461 * @uses $attrArray 462 * @uses $attrBlacklist 463 * @uses $attrMethod 464 * @uses $xssAuto 465 */ 466 protected function filterAttr($attrSet) { 467 $newSet = array(); 468 // process attributes 469 for ($i = 0; $i <count($attrSet); $i++) { 470 // skip blank spaces in tag 471 if (!$attrSet[$i]) continue; 472 // split into attr name and value 473 $attrSubSet = explode('=', trim($attrSet[$i])); 474 list($attrSubSet[0]) = explode(' ', $attrSubSet[0]); 475 // removes all "non-regular" attr names AND also attr blacklisted 476 if ((!eregi("^[a-z]*$",$attrSubSet[0])) || ((self::$xssAuto) && ((in_array(strtolower($attrSubSet[0]), self::$attrBlacklist)) || (substr($attrSubSet[0], 0, 2) == 'on')))) 477 continue; 478 // xss attr value filtering 479 if ($attrSubSet[1] || is_numeric($attrSubSet[1])) { 480 // strips unicode, hex, etc 481 $attrSubSet[1] = str_replace('&#', '', $attrSubSet[1]); 482 // strip normal newline within attr value 483 $attrSubSet[1] = preg_replace('/\s+/', '', $attrSubSet[1]); 484 // strip double quotes 485 $attrSubSet[1] = str_replace('"', '', $attrSubSet[1]); 486 // [requested feature] convert single quotes from either side to doubles (Single quotes shouldn't be used to pad attr value) 487 if ((substr($attrSubSet[1], 0, 1) == "'") && (substr($attrSubSet[1], (strlen($attrSubSet[1]) - 1), 1) == "'")) 488 $attrSubSet[1] = substr($attrSubSet[1], 1, (strlen($attrSubSet[1]) - 2)); 489 // strip slashes 490 $attrSubSet[1] = stripslashes($attrSubSet[1]); 491 } 492 // auto strip attr's with "javascript: 493 if (((strpos(strtolower($attrSubSet[1]), 'expression') !== false) && 494 (strtolower($attrSubSet[0]) == 'style')) || 495 (strpos(strtolower($attrSubSet[1]), 'javascript:') !== false) || 496 (strpos(strtolower($attrSubSet[1]), 'behaviour:') !== false) || 497 (strpos(strtolower($attrSubSet[1]), 'vbscript:') !== false) || 498 (strpos(strtolower($attrSubSet[1]), 'mocha:') !== false) || 499 (strpos(strtolower($attrSubSet[1]), 'livescript:') !== false) 500 ) { continue; } 501 502 // if matches user defined array 503 $attrFound = in_array(strtolower($attrSubSet[0]), self::$attrArray); 504 //error_log("attrFound:".($attrFound ? "Yes" : "No")); 505 // keep this attr on condition 506 if ((!$attrFound && self::$attrMethod) || ($attrFound && !self::$attrMethod)) { 507 //error_log($attrSubSet[0]."=".$attrSubSet[1]); 508 // attr has value 509 if($attrSubSet[1]) { 510 $newSet[] = $attrSubSet[0] . '="' . $attrSubSet[1] . '"'; 511 // attr has decimal zero as value 512 } elseif ($attrSubSet[1] == "0") { 513 $newSet[] = $attrSubSet[0] . '="0"'; 514 // reformat single attributes to XHTML 515 } else { 516 $newSet[] = $attrSubSet[0] . '="' . $attrSubSet[0] . '"'; 517 } 518 } 519 } 520 return $newSet; 521 } 522 523 /** 524 * Convert HTML entities to characters 525 * 526 * Convert input string containing HTML entities to the 527 * corresponding character (& => &). ISO 8859-1 character 528 * set is assumed. 529 * @param string $source Character string containing HTML entities 530 * @return string Input string, with entities converted to characters 531 * @uses chr() 532 * @uses html_entity_decode() 533 * @uses preg_replace() 534 */ 535 protected function decode($source) { 536 // url decode 537 $source = html_entity_decode($source, ENT_QUOTES, "ISO-8859-1"); 538 // convert decimal &#DDD; to character DDD 539 $source = preg_replace('/&#(\d+);/me',"chr(\\1)", $source); 540 // convert hex &#xXXX; to character XXX 541 $source = preg_replace('/&#x([a-f0-9]+);/mei',"chr(0x\\1)", $source); 542 return $source; 543 } 544 } 545 546 // -- set Emacs parameters -- 547 // Local variables: 548 // tab-width: 4 549 // c-basic-offset: 4 550 // c-hanging-comment-ender-p: nil 551 // indent-tabs-mode: nil 552 // End: 553 ?>
titre
Description
Corps
titre
Description
Corps
titre
Description
Corps
titre
Corps
Généré le : Sun Feb 25 20:04:38 2007 | par Balluche grâce à PHPXref 0.7 |