OSEC

Neohapsis is currently accepting applications for employment. For more information, please visit our website www.neohapsis.com or email hr@neohapsis.com
PHP parse_str() arbitrary variable overwrite

gmdarkfiggmail.com
Date: Tue Jun 12 2007 - 08:56:38 CDT


       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.