← Back to all posts
Code Auditing5 min read

PHP Code Auditing: Variable Overwrite

An introductory lesson on auditing PHP code for variable-overwrite vulnerabilities, covering common functions and techniques as well as practical examples.

Read in Chinese→

0x00 Prerequisites

This article was written as a lesson to share with some friends, so it is relatively rough… Please excuse the simplicity. The content is fairly basic and is essentially just a brief teaching outline.

0x01 Auditing for Variable Overwrite Vulnerabilities

0x00 Introduction

Variable overwrite, as the name suggests, means that the value of an existing variable can be overwritten, resulting in a variable-overwrite vulnerability.

Common code-auditing scenarios involve the following keywords appearing in the code:

  • register_globals=on
  • The extract() function
  • The parse_str() function
  • The import_request_variables() function
  • $$

0x01 Variable Overwrite Demonstration

extract()

The extract(array,extract_rules,prefix) function

https://www.runoob.com/php/func-array-extract.html

This function imports variables from an array into the current symbol table. In other words, it registers the array’s key-value pairs as variables, using the array keys as variable names and the corresponding array values as variable values.

Here, we need to pay attention to the function’s second parameter:

  • EXTR_OVERWRITE - Default. If a conflict occurs, the existing variable is overwritten.
  • EXTR_SKIP - If a conflict occurs, the existing variable is not overwritten.

This makes variable overwrite possible.

<?php
$a = 'a';
echo $a.'</br>';
extract($_GET);
echo $a
?>

As we can see, the initial value of our variable is a, but after it is overwritten, it becomes the value we supplied.

http://127.0.0.1/test/extract.php?a=123

image-20200907141301903

Remediation:

When using the extract() function, set its second parameter to EXTR_SKIP.

parse_str()

The parse_str() function is used to parse a query string into variables. If the array parameter is omitted, variables set by this function will overwrite existing variables with the same names.

This function is used here without the array parameter.

In PHP 7.2, calling it without this parameter was deprecated. This function has no return value.

<?php
$a = "giao";
echo "a:" . $a;
echo "<br>";
$b = $_GET['b'];
parse_str($b);
echo "a_2:" . $a;
?>
http://127.0.0.1/test/parse_str.php?b=a=zeo

image-20200907145448737

$$

A typical example is using foreach to iterate over values in an array and use them as variables.

$$ is variable-variable syntax. It allows the value of an ordinary variable to be used as the name of another variable. This pattern is often used in code that iterates over data to create variables.

<?php
$a = 10;
echo $a;
echo "<br>";
foreach ($_POST as $k => $v){
    $k = $v;
    echo $a;
}
?>
```![image-20200907141129332](/images/8c093c65bd382b9c6c5f5dd06977423b.png)

#### **import\_request\_variables\(\)**

**`import_request_variables ( string $types , string $prefix )`**

https://www.runoob.com/php/php-import\_request\_variables-function.html

The **import\_request\_variables\(\)** function imports GET, POST, and Cookie variables into the global scope. **This function is no longer supported in recent versions of PHP.**

The **import\_request\_variables\(\)** function imports GET, POST, and Cookie variables into the global scope. If you have disabled register\_globals but still want to use certain global variables, this function can be useful.

Version requirements: PHP 4 >= 4.1.0, PHP 5 < 5.4.0

**Second parameter, \$types**: Specifies the variables to import. The letters G, P, and C represent GET, POST, and Cookie, respectively. These letters are case-insensitive, so you can use any combination of g, p, and c. POST also includes information about files uploaded using the POST method. Note the order of these letters: when gp is used, POST variables overwrite GET variables with the same names. Any letters other than G, P, and C are ignored.

![image-20200907104858013](/images/51a4fd89763e1ae851d2c354e939e346.png)

#### Global Variables

When upgrading PHP to PHP 5.4 or later, you may notice that the register\_global configuration directive no longer takes effect.

This is because the register\_global configuration directive was removed in PHP 5.4.```php
<?php
echo "register_globals: " . (int)ini_get("register_globals");
echo '<br>';
echo "a=" . $a;
?>
```![image-20200907114032777](/images/79f3b1584af3d084212dc748638eadde.png)

# 0x02 Sangxfor edr Example

Simplified code```php
<?php
#var_dump($_REQUEST);


$show_form = function($params) use(&$strip_slashes, &$show_input) {
    extract($params);

    $host  = isset($host)  ? $strip_slashes($host)  : "127.0.0.1";
    $path  = isset($path)  ? $strip_slashes($path)  : "";
    $row   = isset($row)   ? $strip_slashes($row)   : "";
    $limit = isset($limit) ? $strip_slashes($limit) : 1000;

    // 绘制表单
    echo "<pre>";
    echo '<form id="studio" name="studio" method="post" action="">';
    $show_input(array("title" => "Host ",  "name" => "host",  "value" => $host,  "note" => " - host, e.g. 127.0.0.1"));
    $show_input(array("title" => "Path ",  "name" => "path",  "value" => $path,  "note" => " - path regex, e.g. mapreduce"));
    $show_input(array("title" => "Row  ",  "name" => "row",   "value" => $row,   "note" => " - row regex, e.g. \s[w|e]\s"));
    $show_input(array("title" => "Limit",  "name" => "limit", "value" => $limit, "note" => " - top n, e.g. 100"));
    echo '<input type="submit" id="button">';
    echo '</form>';
    echo "</pre>";
};

$show_form($_REQUEST);

?>
```The anonymous function stored in variable `$show_form` has one formal parameter, `$params`

Here, it is **array\(“strip\_slashes”=>“system”,“host”=>“id”\);**

Next, **extract\(\$params\);** is executed, after which execution enters the following code:```php
$host  = isset($host)  ? $strip_slashes($host)  : "127.0.0.1";

The vulnerability arises during this process. To understand the specific cause:

First, the value passed to the function is array("strip_slashes"=>"system","host"=>"id");.

After being processed by the extract() function, two variables are assigned:

$strip_slashes = 'system';
$host = 'id';

The variable $host is reassigned through a ternary expression to $strip_slashes($host).

In fact, the assigned value is the return value of the system('id') function, resulting in a command-execution vulnerability.

0x03 MetInfo Example

/include/common.inc.php

The supplied Cookie, GET, and POST parameters are assigned to variables.

foreach(array('_COOKIE', '_POST', '_GET') as $_request) {
	foreach($_request as $_key => $_value) {
		$_key{0} != '_' && $_key = daddslashes($_value);
	}
}

daddslashes() provides injection protection, but it does not affect the issue.

Let us examine how an arbitrary subfile is loaded: \about\index.php

<?php
# MetInfo Enterprise Content Management System 
# Copyright (C) MetInfo Co.,Ltd (http://www.metinfo.cn). All rights reserved. 
$filpy = basename(dirname(__FILE__));
$fmodule=1;
require_once '../include/module.php';
require_once $module;
# This program is an open source system, commercial use, please consciously to purchase commercial license.
# Copyright (C) MetInfo Co., Ltd. (http://www.metinfo.cn). All rights reserved.
?>

Here, the require_once function is used to include the /include/module.php file. This file can also be seen to include common.inc.php.

Two unknown variables appear: $module and $fmodule. We can use the $fmodule variable through two file inclusions, then use $_request to obtain the new $fmodule value passed through GET, thereby achieving variable overwrite.