| [ Index ] |
PHP Cross Reference of Joomla 1.5.26 DE |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * @class: InputFilter (PHP4 & PHP5, with comments) 4 * @project: PHP Input Filter 5 * @date: 10-05-2005 6 * @version: 1.2.2_php4/php5 7 * @author: Daniel Morris 8 * @contributors: Gianpaolo Racca, Ghislain Picard, Marco Wandschneider, Chris 9 * Tobin and Andrew Eddie. 10 * 11 * Modification by Louis Landry 12 * 13 * @copyright: Daniel Morris 14 * @email: dan@rootcube.com 15 * @license: GNU General Public License (GPL) 16 */ 17 class InputFilter 18 { 19 var $tagsArray; // default = empty array 20 var $attrArray; // default = empty array 21 22 var $tagsMethod; // default = 0 23 var $attrMethod; // default = 0 24 25 var $xssAuto; // default = 1 26 var $tagBlacklist = array ('applet', 'body', 'bgsound', 'base', 'basefont', 'embed', 'frame', 'frameset', 'head', 'html', 'id', 'iframe', 'ilayer', 'layer', 'link', 'meta', 'name', 'object', 'script', 'style', 'title', 'xml'); 27 var $attrBlacklist = array ('action', 'background', 'codebase', 'dynsrc', 'lowsrc'); // also will strip ALL event handlers 28 29 /** 30 * Constructor for inputFilter class. Only first parameter is required. 31 * 32 * @access protected 33 * @param array $tagsArray list of user-defined tags 34 * @param array $attrArray list of user-defined attributes 35 * @param int $tagsMethod WhiteList method = 0, BlackList method = 1 36 * @param int $attrMethod WhiteList method = 0, BlackList method = 1 37 * @param int $xssAuto Only auto clean essentials = 0, Allow clean 38 * blacklisted tags/attr = 1 39 */ 40 function inputFilter($tagsArray = array (), $attrArray = array (), $tagsMethod = 0, $attrMethod = 0, $xssAuto = 1) 41 { 42 /* 43 * Make sure user defined arrays are in lowercase 44 */ 45 $tagsArray = array_map('strtolower', (array) $tagsArray); 46 $attrArray = array_map('strtolower', (array) $attrArray); 47 48 /* 49 * Assign member variables 50 */ 51 $this->tagsArray = $tagsArray; 52 $this->attrArray = $attrArray; 53 $this->tagsMethod = $tagsMethod; 54 $this->attrMethod = $attrMethod; 55 $this->xssAuto = $xssAuto; 56 } 57 58 /** 59 * Method to be called by another php script. Processes for XSS and 60 * specified bad code. 61 * 62 * @access public 63 * @param mixed $source Input string/array-of-string to be 'cleaned' 64 * @return mixed $source 'cleaned' version of input parameter 65 */ 66 function process($source) 67 { 68 /* 69 * Are we dealing with an array? 70 */ 71 if (is_array($source)) 72 { 73 foreach ($source as $key => $value) 74 { 75 // filter element for XSS and other 'bad' code etc. 76 if (is_string($value)) 77 { 78 $source[$key] = $this->remove($this->decode($value)); 79 } 80 } 81 return $source; 82 } else 83 /* 84 * Or a string? 85 */ 86 if (is_string($source) && !empty ($source)) 87 { 88 // filter source for XSS and other 'bad' code etc. 89 return $this->remove($this->decode($source)); 90 } else 91 { 92 /* 93 * Not an array or string.. return the passed parameter 94 */ 95 return $source; 96 } 97 } 98 99 /** 100 * Internal method to iteratively remove all unwanted tags and attributes 101 * 102 * @access protected 103 * @param string $source Input string to be 'cleaned' 104 * @return string $source 'cleaned' version of input parameter 105 */ 106 function remove($source) 107 { 108 $loopCounter = 0; 109 /* 110 * Iteration provides nested tag protection 111 */ 112 while ($source != $this->filterTags($source)) 113 { 114 $source = $this->filterTags($source); 115 $loopCounter ++; 116 } 117 return $source; 118 } 119 120 /** 121 * Internal method to strip a string of certain tags 122 * 123 * @access protected 124 * @param string $source Input string to be 'cleaned' 125 * @return string $source 'cleaned' version of input parameter 126 */ 127 function filterTags($source) 128 { 129 /* 130 * In the beginning we don't really have a tag, so everything is 131 * postTag 132 */ 133 $preTag = null; 134 $postTag = $source; 135 136 /* 137 * Is there a tag? If so it will certainly start with a '<' 138 */ 139 $tagOpen_start = strpos($source, '<'); 140 141 while ($tagOpen_start !== false) 142 { 143 144 /* 145 * Get some information about the tag we are processing 146 */ 147 $preTag .= substr($postTag, 0, $tagOpen_start); 148 $postTag = substr($postTag, $tagOpen_start); 149 $fromTagOpen = substr($postTag, 1); 150 $tagOpen_end = strpos($fromTagOpen, '>'); 151 152 /* 153 * Let's catch any non-terminated tags and skip over them 154 */ 155 if ($tagOpen_end === false) 156 { 157 $postTag = substr($postTag, $tagOpen_start +1); 158 $tagOpen_start = strpos($postTag, '<'); 159 continue; 160 } 161 162 /* 163 * Do we have a nested tag? 164 */ 165 $tagOpen_nested = strpos($fromTagOpen, '<'); 166 $tagOpen_nested_end = strpos(substr($postTag, $tagOpen_end), '>'); 167 if (($tagOpen_nested !== false) && ($tagOpen_nested < $tagOpen_end)) 168 { 169 $preTag .= substr($postTag, 0, ($tagOpen_nested +1)); 170 $postTag = substr($postTag, ($tagOpen_nested +1)); 171 $tagOpen_start = strpos($postTag, '<'); 172 continue; 173 } 174 175 176 /* 177 * Lets get some information about our tag and setup attribute pairs 178 */ 179 $tagOpen_nested = (strpos($fromTagOpen, '<') + $tagOpen_start +1); 180 $currentTag = substr($fromTagOpen, 0, $tagOpen_end); 181 $tagLength = strlen($currentTag); 182 $tagLeft = $currentTag; 183 $attrSet = array (); 184 $currentSpace = strpos($tagLeft, ' '); 185 186 /* 187 * Are we an open tag or a close tag? 188 */ 189 if (substr($currentTag, 0, 1) == "/") 190 { 191 // Close Tag 192 $isCloseTag = true; 193 list ($tagName) = explode(' ', $currentTag); 194 $tagName = substr($tagName, 1); 195 } else 196 { 197 // Open Tag 198 $isCloseTag = false; 199 list ($tagName) = explode(' ', $currentTag); 200 } 201 202 /* 203 * Exclude all "non-regular" tagnames 204 * OR no tagname 205 * OR remove if xssauto is on and tag is blacklisted 206 */ 207 if ((!preg_match("/^[a-z][a-z0-9]*$/i", $tagName)) || (!$tagName) || ((in_array(strtolower($tagName), $this->tagBlacklist)) && ($this->xssAuto))) 208 { 209 $postTag = substr($postTag, ($tagLength +2)); 210 $tagOpen_start = strpos($postTag, '<'); 211 // Strip tag 212 continue; 213 } 214 215 /* 216 * Time to grab any attributes from the tag... need this section in 217 * case attributes have spaces in the values. 218 */ 219 while ($currentSpace !== false) 220 { 221 $fromSpace = substr($tagLeft, ($currentSpace +1)); 222 $nextSpace = strpos($fromSpace, ' '); 223 $openQuotes = strpos($fromSpace, '"'); 224 $closeQuotes = strpos(substr($fromSpace, ($openQuotes +1)), '"') + $openQuotes +1; 225 226 /* 227 * Do we have an attribute to process? [check for equal sign] 228 */ 229 if (strpos($fromSpace, '=') !== false) 230 { 231 /* 232 * If the attribute value is wrapped in quotes we need to 233 * grab the substring from the closing quote, otherwise grab 234 * till the next space 235 */ 236 if (($openQuotes !== false) && (strpos(substr($fromSpace, ($openQuotes +1)), '"') !== false)) 237 { 238 $attr = substr($fromSpace, 0, ($closeQuotes +1)); 239 } else 240 { 241 $attr = substr($fromSpace, 0, $nextSpace); 242 } 243 } else 244 { 245 /* 246 * No more equal signs so add any extra text in the tag into 247 * the attribute array [eg. checked] 248 */ 249 $attr = substr($fromSpace, 0, $nextSpace); 250 } 251 252 // Last Attribute Pair 253 if (!$attr) 254 { 255 $attr = $fromSpace; 256 } 257 258 /* 259 * Add attribute pair to the attribute array 260 */ 261 $attrSet[] = $attr; 262 263 /* 264 * Move search point and continue iteration 265 */ 266 $tagLeft = substr($fromSpace, strlen($attr)); 267 $currentSpace = strpos($tagLeft, ' '); 268 } 269 270 /* 271 * Is our tag in the user input array? 272 */ 273 $tagFound = in_array(strtolower($tagName), $this->tagsArray); 274 275 /* 276 * If the tag is allowed lets append it to the output string 277 */ 278 if ((!$tagFound && $this->tagsMethod) || ($tagFound && !$this->tagsMethod)) 279 { 280 /* 281 * Reconstruct tag with allowed attributes 282 */ 283 if (!$isCloseTag) 284 { 285 // Open or Single tag 286 $attrSet = $this->filterAttr($attrSet); 287 $preTag .= '<'.$tagName; 288 for ($i = 0; $i < count($attrSet); $i ++) 289 { 290 $preTag .= ' '.$attrSet[$i]; 291 } 292 293 /* 294 * Reformat single tags to XHTML 295 */ 296 if (strpos($fromTagOpen, "</".$tagName)) 297 { 298 $preTag .= '>'; 299 } else 300 { 301 $preTag .= ' />'; 302 } 303 } else 304 { 305 // Closing Tag 306 $preTag .= '</'.$tagName.'>'; 307 } 308 } 309 310 /* 311 * Find next tag's start and continue iteration 312 */ 313 $postTag = substr($postTag, ($tagLength +2)); 314 $tagOpen_start = strpos($postTag, '<'); 315 } 316 317 /* 318 * Append any code after the end of tags and return 319 */ 320 if ($postTag != '<') 321 { 322 $preTag .= $postTag; 323 } 324 return $preTag; 325 } 326 327 /** 328 * Internal method to strip a tag of certain attributes 329 * 330 * @access protected 331 * @param array $attrSet Array of attribute pairs to filter 332 * @return array $newSet Filtered array of attribute pairs 333 */ 334 function filterAttr($attrSet) 335 { 336 /* 337 * Initialize variables 338 */ 339 $newSet = array (); 340 341 /* 342 * Iterate through attribute pairs 343 */ 344 for ($i = 0; $i < count($attrSet); $i ++) 345 { 346 /* 347 * Skip blank spaces 348 */ 349 if (!$attrSet[$i]) 350 { 351 continue; 352 } 353 354 /* 355 * Split into name/value pairs 356 */ 357 $attrSubSet = explode('=', trim($attrSet[$i]), 2); 358 list ($attrSubSet[0]) = explode(' ', $attrSubSet[0]); 359 360 /* 361 * Remove all "non-regular" attribute names 362 * AND blacklisted attributes 363 */ 364 if ((!preg_match("#^[a-z]*$#i", $attrSubSet[0])) || (($this->xssAuto) && ((in_array(strtolower($attrSubSet[0]), $this->attrBlacklist)) || (substr($attrSubSet[0], 0, 2) == 'on')))) 365 { 366 continue; 367 } 368 369 /* 370 * XSS attribute value filtering 371 */ 372 if ($attrSubSet[1]) 373 { 374 // strips unicode, hex, etc 375 $attrSubSet[1] = str_replace('&#', '', $attrSubSet[1]); 376 // strip normal newline within attr value 377 $attrSubSet[1] = preg_replace('/\s+/', '', $attrSubSet[1]); 378 // strip double quotes 379 $attrSubSet[1] = str_replace('"', '', $attrSubSet[1]); 380 // [requested feature] convert single quotes from either side to doubles (Single quotes shouldn't be used to pad attr value) 381 if ((substr($attrSubSet[1], 0, 1) == "'") && (substr($attrSubSet[1], (strlen($attrSubSet[1]) - 1), 1) == "'")) 382 { 383 $attrSubSet[1] = substr($attrSubSet[1], 1, (strlen($attrSubSet[1]) - 2)); 384 } 385 // strip slashes 386 $attrSubSet[1] = stripslashes($attrSubSet[1]); 387 } 388 389 /* 390 * Autostrip script tags 391 */ 392 if (InputFilter::badAttributeValue($attrSubSet)) 393 { 394 continue; 395 } 396 397 /* 398 * Is our attribute in the user input array? 399 */ 400 $attrFound = in_array(strtolower($attrSubSet[0]), $this->attrArray); 401 402 /* 403 * If the tag is allowed lets keep it 404 */ 405 if ((!$attrFound && $this->attrMethod) || ($attrFound && !$this->attrMethod)) 406 { 407 /* 408 * Does the attribute have a value? 409 */ 410 if ($attrSubSet[1]) 411 { 412 $newSet[] = $attrSubSet[0].'="'.$attrSubSet[1].'"'; 413 } 414 elseif ($attrSubSet[1] == "0") 415 { 416 /* 417 * Special Case 418 * Is the value 0? 419 */ 420 $newSet[] = $attrSubSet[0].'="0"'; 421 } else 422 { 423 $newSet[] = $attrSubSet[0].'="'.$attrSubSet[0].'"'; 424 } 425 } 426 } 427 return $newSet; 428 } 429 430 /** 431 * Function to determine if contents of an attribute is safe 432 * 433 * @access protected 434 * @param array $attrSubSet A 2 element array for attributes name,value 435 * @return boolean True if bad code is detected 436 */ 437 function badAttributeValue($attrSubSet) 438 { 439 $attrSubSet[0] = strtolower($attrSubSet[0]); 440 $attrSubSet[1] = strtolower($attrSubSet[1]); 441 return (((strpos($attrSubSet[1], 'expression') !== false) && ($attrSubSet[0]) == 'style') || (strpos($attrSubSet[1], 'javascript:') !== false) || (strpos($attrSubSet[1], 'behaviour:') !== false) || (strpos($attrSubSet[1], 'vbscript:') !== false) || (strpos($attrSubSet[1], 'mocha:') !== false) || (strpos($attrSubSet[1], 'livescript:') !== false)); 442 } 443 444 /** 445 * Try to convert to plaintext 446 * 447 * @access protected 448 * @param string $source 449 * @return string Plaintext string 450 */ 451 function decode($source) 452 { 453 // url decode 454 $source = html_entity_decode($source, ENT_QUOTES, "ISO-8859-1"); 455 // convert decimal 456 $source = preg_replace('/&#(\d+);/me', "chr(\\1)", $source); // decimal notation 457 // convert hex 458 $source = preg_replace('/&#x([a-f0-9]+);/mei', "chr(0x\\1)", $source); // hex notation 459 return $source; 460 } 461 462 /** 463 * Method to be called by another php script. Processes for SQL injection 464 * 465 * @access public 466 * @param mixed $source input string/array-of-string to be 'cleaned' 467 * @param resource $connection - An open MySQL connection 468 * @return string 'cleaned' version of input parameter 469 */ 470 function safeSQL($source, & $connection) 471 { 472 // clean all elements in this array 473 if (is_array($source)) 474 { 475 foreach ($source as $key => $value) 476 { 477 // filter element for SQL injection 478 if (is_string($value)) 479 { 480 $source[$key] = $this->quoteSmart($this->decode($value), $connection); 481 } 482 } 483 return $source; 484 // clean this string 485 } else 486 if (is_string($source)) 487 { 488 // filter source for SQL injection 489 if (is_string($source)) 490 { 491 return $this->quoteSmart($this->decode($source), $connection); 492 } 493 // return parameter as given 494 } else 495 { 496 return $source; 497 } 498 } 499 500 /** 501 * Method to escape a string 502 * 503 * @author Chris Tobin 504 * @author Daniel Morris 505 * 506 * @access protected 507 * @param string $source 508 * @param resource $connection An open MySQL connection 509 * @return string Escaped string 510 */ 511 function quoteSmart($source, & $connection) 512 { 513 /* 514 * Strip escaping slashes if necessary 515 */ 516 if (get_magic_quotes_gpc()) 517 { 518 $source = stripslashes($source); 519 } 520 521 /* 522 * Escape numeric and text values 523 */ 524 $source = $this->escapeString($source, $connection); 525 return $source; 526 } 527 528 /** 529 * @author Chris Tobin 530 * @author Daniel Morris 531 * 532 * @access protected 533 * @param string $source 534 * @param resource $connection An open MySQL connection 535 * @return string Escaped string 536 */ 537 function escapeString($string, & $connection) { 538 /* 539 * Use the appropriate escape string depending upon which version of php 540 * you are running 541 */ 542 if (version_compare(phpversion(), '4.3.0', '<')) { 543 $string = mysql_escape_string($string); 544 } else { 545 $string = mysql_real_escape_string($string); 546 } 547 548 return $string; 549 } 550 } 551 ?>
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Wed Mar 28 15:54:07 2012 | Cross-referenced by PHPXref 0.7.1 |