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.