<<< Date Index >>>     <<< Thread Index >>>

PHP parse_str() arbitrary variable overwrite



       Title:    PHP parse_str() arbitrary variable overwrite
      Vendor:    http://www.php.net/
    Advisory:    http://www.acid-root.new.fr/advisories/14070612.txt
      Author:    DarkFig < gmdarkfig (at) gmail (dot) com >
  Written on:    2007/06/12
 Released on:    2007/06/12
  Risk level:    Medium / High



 [I].BACKGROUND

 [Quote from php.net] PHP is a popular open-source programming
 language used primarily for developing server-side applications
 and dynamic web content, and more recently, other software. The
 name is a recursive acronym for "PHP: Hypertext Preprocessor".
 This is actually a retronym; see history of PHP.[/quote]

 While I was coding the new version of phpsploitclass, I was
 reading the manual of parse_url(), then I saw the parse_str()
 function. I decided to see how it works. During some test that
 I did, I discovered a vulnerability which can be exploited to
 overwrite some variables.



 [II].MANUAL

 void parse_str ( string $str [, array &$arr] )

 Parses str as if it were the query string passed via a URL and
 sets variables in the current scope. If the second parameter arr
 is present, variables are stored in this variable as array
 elements instead.

 Note: Support for the optional second parameter was added in
 PHP 4.0.3. 
 
 Note: To get the current QUERY_STRING, you may use the variable
 $_SERVER['QUERY_STRING']. Also, you may want to read the section
 on variables from outside of PHP. 

 Note: The magic_quotes_gpc setting affects the output of this
 function, as parse_str() uses the same mechanism that PHP uses
 to populate the $_GET, $_POST, etc. variables. 



 [III].SOURCE CODE

 --- ./ext/standard/string.c ---
 4025. /*
 4025. {{{ proto void parse_str(string encoded_string [, array result])
 4026. Parses GET/POST/COOKIE data and sets global variables
 4026. */
 4027. PHP_FUNCTION(parse_str)
 4028. {
 4029.   zval **arg;
 4030.   zval **arrayArg;
 4031.   zval *sarg;
 4032.   char *res = NULL;
 4033.   int argCount;
 4034.   
 4035.   argCount = ZEND_NUM_ARGS();
 4036.   if (argCount < 1 ||
 4036.       argCount > 2 ||
 4036.       zend_get_parameters_ex(argCount,&arg,&arrayArg) == FAILURE)
 4036.   {
 ####.          /* Not enough or too many args */
 4037.          WRONG_PARAM_COUNT;
 4038.   }
 4039. 
 4040.  convert_to_string_ex(arg);
 4041.  sarg = *arg;
 4042.  if (Z_STRVAL_P(sarg) && *Z_STRVAL_P(sarg)) {
 4043.          res = estrndup(Z_STRVAL_P(sarg), Z_STRLEN_P(sarg));
 4043.
 ####.  /* Allocate Z_STRLEN_P(sarg)+1 bytes of memory and copy
 ####.     Z_STRLEN_P(sarg) bytes from Z_STRVAL_P(sarg) to the
 ####.     newly allocated block */
 4044.  }
 4045.
 ####.  /* parse_str(argv1) */
 4046.  if (argCount == 1)
 4046.  {
 4047.          zval tmp;
 4048.          Z_ARRVAL(tmp) = EG(active_symbol_table);
 4049.
 ####.  /* The problem is here, there is no conditions before setting
 ####.     the variable. If a variable already exists, it will overwrite it */
 4049.
 4050.          sapi_module.treat_data(PARSE_STRING, res, &tmp TSRMLS_CC);
 4051.  }
 ####.  /* parse_str(argv1,argv2) */
 4051.  else
 4051.  {
 4052.          /* Clear out the array that was passed in. */
 4053.          zval_dtor(*arrayArg);
 4054.          array_init(*arrayArg);
 4055.          
 4056.          sapi_module.treat_data(PARSE_STRING, res, *arrayArg TSRMLS_CC);
 4057.  }
 4058. }



 [IV].EXPLANATIONS

 As you can see in the manual, the user who want to use this
 function is not prevented against overwriting. That's why I
 think that overwriting is not wanted, they forgot to check it.
 Simple proof of concept:

 <?php
 
 # ?var=new
 ###########
 $var   = 'init';                     #
 parse_str($_SERVER['QUERY_STRING']); #
 print $var;                          # new

 # ?array[]=new                       # Array
 ##############                       # (
 $array = array('init');              #    [0] => init
 parse_str($_SERVER['QUERY_STRING']); #    [1] => new
 print_r($array);                     # )

 # ?array=new
 ############                                # Array
 $array = array('init');                     # (
 parse_str($_SERVER['QUERY_STRING'],$array); #    [array] => new
 print_r($array);                            # )

 ?>

 This type of vulnerability can open a door to many vulnerabilities,
 that's why it's difficult to define the risk level. Unlike extract()
 there is no option such as EXTR_SKIP which will define what to do
 if there is a collision. So if you want to secure your code, don't
 use this function. I didn't contacted the php team but maybe they
 will release a fix for this vulnerability.



 [V].GREETZ

 benjilenoob, ddx39, lorenzo, romano, shaka, sparah.