[ Index ]

PHP Cross Reference of Mambo 4.6.5

[ Variables ]     [ Functions ]     [ Classes ]     [ Constants ]     [ Statistics ]

title

Body

[close]

/includes/phpInputFilter/ -> class.inputfilter.php (source)

   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 Tobin and Andrew Eddie.
   9    * @copyright: Daniel Morris
  10    * @email: dan@rootcube.com
  11    * @license: GNU General Public License (GPL)
  12    */
  13  class InputFilter {
  14      var $tagsArray;            // default = empty array
  15      var $attrArray;            // default = empty array
  16  
  17      var $tagsMethod;        // default = 0
  18      var $attrMethod;        // default = 0
  19  
  20      var $xssAuto;           // default = 1
  21      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');
  22      var $attrBlacklist = array('action', 'background', 'codebase', 'dynsrc', 'lowsrc');  // also will strip ALL event handlers
  23  
  24      /**
  25        * Constructor for inputFilter class. Only first parameter is required.
  26        * @access constructor
  27        * @param Array $tagsArray - list of user-defined tags
  28        * @param Array $attrArray - list of user-defined attributes
  29        * @param int $tagsMethod - 0= allow just user-defined, 1= allow all but user-defined
  30        * @param int $attrMethod - 0= allow just user-defined, 1= allow all but user-defined
  31        * @param int $xssAuto - 0= only auto clean essentials, 1= allow clean blacklisted tags/attr
  32        */
  33  	function inputFilter($tagsArray = array(), $attrArray = array(), $tagsMethod = 0, $attrMethod = 0, $xssAuto = 1) {
  34          // make sure user defined arrays are in lowercase
  35          for ($i = 0; $i < count($tagsArray); $i++) $tagsArray[$i] = strtolower($tagsArray[$i]);
  36          for ($i = 0; $i < count($attrArray); $i++) $attrArray[$i] = strtolower($attrArray[$i]);
  37          // assign to member vars
  38          $this->tagsArray = (array) $tagsArray;
  39          $this->attrArray = (array) $attrArray;
  40          $this->tagsMethod = $tagsMethod;
  41          $this->attrMethod = $attrMethod;
  42          $this->xssAuto = $xssAuto;
  43      }
  44  
  45      /**
  46        * Method to be called by another php script. Processes for XSS and specified bad code.
  47        * @access public
  48        * @param Mixed $source - input string/array-of-string to be 'cleaned'
  49        * @return String $source - 'cleaned' version of input parameter
  50        */
  51  	function process($source) {
  52          // clean all elements in this array
  53          if (is_array($source)) {
  54              foreach($source as $key => $value)
  55                  // filter element for XSS and other 'bad' code etc.
  56                  if (is_string($value)) $source[$key] = $this->remove($this->decode($value));
  57              return $source;
  58          // clean this string
  59          } else if (is_string($source)) {
  60              // filter source for XSS and other 'bad' code etc.
  61              return $this->remove($this->decode($source));
  62          // return parameter as given
  63          } else return $source;
  64      }
  65  
  66      /**
  67        * Internal method to iteratively remove all unwanted tags and attributes
  68        * @access protected
  69        * @param String $source - input string to be 'cleaned'
  70        * @return String $source - 'cleaned' version of input parameter
  71        */
  72  	function remove($source) {
  73          $loopCounter=0;
  74          // provides nested-tag protection
  75          while($source != $this->filterTags($source)) {
  76              $source = $this->filterTags($source);
  77              $loopCounter++;
  78          }
  79          return $source;
  80      }
  81  
  82      /**
  83        * Internal method to strip a string of certain tags
  84        * @access protected
  85        * @param String $source - input string to be 'cleaned'
  86        * @return String $source - 'cleaned' version of input parameter
  87        */
  88  	function filterTags($source) {
  89          // filter pass setup
  90          $preTag = NULL;
  91          $postTag = $source;
  92          // find initial tag's position
  93          $tagOpen_start = strpos($source, '<');
  94          // interate through string until no tags left
  95          while($tagOpen_start !== FALSE) {
  96              // process tag interatively
  97              $preTag .= substr($postTag, 0, $tagOpen_start);
  98              $postTag = substr($postTag, $tagOpen_start);
  99              $fromTagOpen = substr($postTag, 1);
 100              // end of tag
 101              $tagOpen_end = strpos($fromTagOpen, '>');
 102              if ($tagOpen_end === false) break;
 103              // next start of tag (for nested tag assessment)
 104              $tagOpen_nested = strpos($fromTagOpen, '<');
 105              if (($tagOpen_nested !== false) && ($tagOpen_nested < $tagOpen_end)) {
 106                  $preTag .= substr($postTag, 0, ($tagOpen_nested+1));
 107                  $postTag = substr($postTag, ($tagOpen_nested+1));
 108                  $tagOpen_start = strpos($postTag, '<');
 109                  continue;
 110              }
 111              $tagOpen_nested = (strpos($fromTagOpen, '<') + $tagOpen_start + 1);
 112              $currentTag = substr($fromTagOpen, 0, $tagOpen_end);
 113              $tagLength = strlen($currentTag);
 114              if (!$tagOpen_end) {
 115                  $preTag .= $postTag;
 116                  $tagOpen_start = strpos($postTag, '<');
 117              }
 118              // iterate through tag finding attribute pairs - setup
 119              $tagLeft = $currentTag;
 120              $attrSet = array();
 121              $currentSpace = strpos($tagLeft, ' ');
 122              // is end tag
 123              if (substr($currentTag, 0, 1) == "/") {
 124                  $isCloseTag = TRUE;
 125                  list($tagName) = explode(' ', $currentTag);
 126                  $tagName = substr($tagName, 1);
 127              // is start tag
 128              } else {
 129                  $isCloseTag = FALSE;
 130                  list($tagName) = explode(' ', $currentTag);
 131              }
 132              // excludes all "non-regular" tagnames OR no tagname OR remove if xssauto is on and tag is blacklisted
 133              if ((!preg_match("/^[a-z][a-z0-9]*$/i",$tagName)) || (!$tagName) || ((in_array(strtolower($tagName), $this->tagBlacklist)) && ($this->xssAuto))) {
 134                  $postTag = substr($postTag, ($tagLength + 2));
 135                  $tagOpen_start = strpos($postTag, '<');
 136                  // don't append this tag
 137                  continue;
 138              }
 139              // this while is needed to support attribute values with spaces in!
 140              while ($currentSpace !== FALSE) {
 141                  $fromSpace = substr($tagLeft, ($currentSpace+1));
 142                  $nextSpace = strpos($fromSpace, ' ');
 143                  $openQuotes = strpos($fromSpace, '"');
 144                  $closeQuotes = strpos(substr($fromSpace, ($openQuotes+1)), '"') + $openQuotes + 1;
 145                  // another equals exists
 146                  if (strpos($fromSpace, '=') !== FALSE) {
 147                      // opening and closing quotes exists
 148                      if (($openQuotes !== FALSE) && (strpos(substr($fromSpace, ($openQuotes+1)), '"') !== FALSE))
 149                          $attr = substr($fromSpace, 0, ($closeQuotes+1));
 150                      // one or neither exist
 151                      else $attr = substr($fromSpace, 0, $nextSpace);
 152                  // no more equals exist
 153                  } else $attr = substr($fromSpace, 0, $nextSpace);
 154                  // last attr pair
 155                  if (!$attr) $attr = $fromSpace;
 156                  // add to attribute pairs array
 157                  $attrSet[] = $attr;
 158                  // next inc
 159                  $tagLeft = substr($fromSpace, strlen($attr));
 160                  $currentSpace = strpos($tagLeft, ' ');
 161              }
 162              // appears in array specified by user
 163              $tagFound = in_array(strtolower($tagName), $this->tagsArray);
 164              // remove this tag on condition
 165              if ((!$tagFound && $this->tagsMethod) || ($tagFound && !$this->tagsMethod)) {
 166                  // reconstruct tag with allowed attributes
 167                  if (!$isCloseTag) {
 168                      $attrSet = $this->filterAttr($attrSet);
 169                      $preTag .= '<' . $tagName;
 170                      for ($i = 0; $i < count($attrSet); $i++)
 171                          $preTag .= ' ' . $attrSet[$i];
 172                      // reformat single tags to XHTML
 173                      if (strpos($fromTagOpen, "</" . $tagName)) $preTag .= '>';
 174                      else $preTag .= ' />';
 175                  // just the tagname
 176                  } else $preTag .= '</' . $tagName . '>';
 177              }
 178              // find next tag's start
 179              $postTag = substr($postTag, ($tagLength + 2));
 180              $tagOpen_start = strpos($postTag, '<');
 181          }
 182          // append any code after end of tags
 183          $preTag .= $postTag;
 184          return $preTag;
 185      }
 186  
 187      /**
 188        * Internal method to strip a tag of certain attributes
 189        * @access protected
 190        * @param Array $attrSet
 191        * @return Array $newSet
 192        */
 193  	function filterAttr($attrSet) {
 194          $newSet = array();
 195          // process attributes
 196          for ($i = 0; $i <count($attrSet); $i++) {
 197              // skip blank spaces in tag
 198              if (!$attrSet[$i]) continue;
 199              // split into attr name and value
 200              $attrSubSet = explode('=', trim($attrSet[$i]),2);
 201              list($attrSubSet[0]) = explode(' ', $attrSubSet[0]);
 202              // removes all "non-regular" attr names AND also attr blacklisted
 203              if ((!eregi("^[a-z]*$",$attrSubSet[0])) || (($this->xssAuto) && ((in_array(strtolower($attrSubSet[0]), $this->attrBlacklist)) || (substr($attrSubSet[0], 0, 2) == 'on'))))
 204                  continue;
 205              // xss attr value filtering
 206              if ($attrSubSet[1]) {
 207                  // strips unicode, hex, etc
 208                  $attrSubSet[1] = str_replace('&#', '', $attrSubSet[1]);
 209                  // strip normal newline within attr value
 210                  $attrSubSet[1] = preg_replace('/\s+/', '', $attrSubSet[1]);
 211                  // strip double quotes
 212                  $attrSubSet[1] = str_replace('"', '', $attrSubSet[1]);
 213                  // [requested feature] convert single quotes from either side to doubles (Single quotes shouldn't be used to pad attr value)
 214                  if ((substr($attrSubSet[1], 0, 1) == "'") && (substr($attrSubSet[1], (strlen($attrSubSet[1]) - 1), 1) == "'"))
 215                      $attrSubSet[1] = substr($attrSubSet[1], 1, (strlen($attrSubSet[1]) - 2));
 216                  // strip slashes
 217                  $attrSubSet[1] = stripslashes($attrSubSet[1]);
 218              }
 219              // auto strip attr's with "javascript:
 220              if (InputFilter::badAttributeValue( $attrSubSet ))
 221                  continue;
 222  
 223              // if matches user defined array
 224              $attrFound = in_array(strtolower($attrSubSet[0]), $this->attrArray);
 225              // keep this attr on condition
 226              if ((!$attrFound && $this->attrMethod) || ($attrFound && !$this->attrMethod)) {
 227                  // attr has value
 228                  if ($attrSubSet[1]) $newSet[] = $attrSubSet[0] . '="' . $attrSubSet[1] . '"';
 229                  // attr has decimal zero as value
 230                  else if ($attrSubSet[1] == "0") $newSet[] = $attrSubSet[0] . '="0"';
 231                  // reformat single attributes to XHTML
 232                  else $newSet[] = $attrSubSet[0] . '="' . $attrSubSet[0] . '"';
 233              }
 234          }
 235          return $newSet;
 236      }
 237  
 238      /**
 239       * Function to determine if contents of an attribute is safe
 240       * @param Array A 2 element array for attribute [name] and [value]
 241       * @return Boolean True if bad code is detected
 242       */
 243  	function badAttributeValue( $attrSubSet ) {
 244          $attrSubSet[0] = strtolower( $attrSubSet[0] );
 245          $attrSubSet[1] = strtolower( $attrSubSet[1] );
 246          return (
 247              ((strpos($attrSubSet[1], 'expression') !== false) && ($attrSubSet[0]) == 'style') ||
 248              (strpos($attrSubSet[1], 'javascript:') !== false) ||
 249              (strpos($attrSubSet[1], 'behaviour:') !== false) ||
 250              (strpos($attrSubSet[1], 'vbscript:') !== false) ||
 251              (strpos($attrSubSet[1], 'mocha:') !== false) ||
 252              (strpos($attrSubSet[1], 'livescript:') !== false)
 253          );
 254      }
 255  
 256      /**
 257        * Try to convert to plaintext
 258        * @access protected
 259        * @param String $source
 260        * @return String $source
 261        */
 262  	function decode($source) {
 263          // url decode
 264          $source = html_entity_decode($source, ENT_QUOTES, "ISO-8859-1");
 265          // convert decimal
 266          $source = preg_replace('/&#(\d+);/me',"chr(\\1)", $source);                // decimal notation
 267          // convert hex
 268          $source = preg_replace('/&#x([a-f0-9]+);/mei',"chr(0x\\1)", $source);    // hex notation
 269          return $source;
 270      }
 271  
 272      /**
 273        * Method to be called by another php script. Processes for SQL injection
 274        * @access public
 275        * @param Mixed $source - input string/array-of-string to be 'cleaned'
 276        * @param Buffer $connection - An open MySQL connection
 277        * @return String $source - 'cleaned' version of input parameter
 278        */
 279  	function safeSQL($source, &$connection) {
 280          // clean all elements in this array
 281          if (is_array($source)) {
 282              foreach($source as $key => $value)
 283                  // filter element for SQL injection
 284                  if (is_string($value)) $source[$key] = $this->quoteSmart($this->decode($value), $connection);
 285              return $source;
 286          // clean this string
 287          } else if (is_string($source)) {
 288              // filter source for SQL injection
 289              if (is_string($source)) return $this->quoteSmart($this->decode($source), $connection);
 290          // return parameter as given
 291          } else return $source;
 292      }
 293  
 294      /**
 295        * @author Chris Tobin
 296        * @author Daniel Morris
 297        * @access protected
 298        * @param String $source
 299        * @param Resource $connection - An open MySQL connection
 300        * @return String $source
 301        */
 302  	function quoteSmart($source, &$connection) {
 303          // strip slashes
 304          if (get_magic_quotes_gpc()) $source = stripslashes($source);
 305          // quote both numeric and text
 306          $source = $this->escapeString($source, $connection);
 307          return $source;
 308      }
 309  
 310      /**
 311        * @author Chris Tobin
 312        * @author Daniel Morris
 313        * @access protected
 314        * @param String $source
 315        * @param Resource $connection - An open MySQL connection
 316        * @return String $source
 317        */
 318  	function escapeString($string, &$connection) {
 319          // depreciated function
 320          if (version_compare(phpversion(),"4.3.0", "<")) mysql_escape_string($string);
 321          // current function
 322          else mysql_real_escape_string($string);
 323          return $string;
 324      }
 325  }
 326  
 327  ?>