# 变量覆盖

***

**相关文章 & Source & Reference**

* <https://www.mi1k7ea.com/2019/06/20/PHP%E5%8F%98%E9%87%8F%E8%A6%86%E7%9B%96%E6%BC%8F%E6%B4%9E/>
* <https://www.jianshu.com/p/a4d782e91852>

***

## $$导致的变量覆盖问题

**$$介绍**

$$$
一个可变变量获取了一个普通变量的值作为这个可变变量的变量名。

```php
<?php
$a = "hello";
echo "$a";          //输出hello
$a="world";
echo "$a";          //输出hello
echo "$$a";         //输出word
echo "$a ${$a}";    //输出hello world
echo "$a $hello";   //输出hello world
?>
```

**漏洞产生**

使用 foreach 来遍历数组中的值，然后再将获取到的数组键名作为变量，数组中的键值作为变量的值。因此就产生了变量覆盖漏洞。

例如

```php
<?php
foreach ($_GET as $key => $value) {
${$key} = $value;
}
echo $a;
?>
```

get 得到的数据 $key 和 $value, 关键第 3 行,${$key} 用 get 传进来的 $key 做为新的变量, 将 get 传进来的 $value 赋值给它。

get ?a=1 第 3 行会解析为 $a=1。就造成了变量覆盖。

***

## extract()函数使用不当

extract()函数从数组中将变量导入到当前的符号表。该函数使用数组键名作为变量名，使用数组键值作为变量值。针对数组中的每个元素，将在当前符号表中创建对应的一个变量。

`extract(array &$array, int $flags = EXTR_OVERWRITE, string $prefix = ""): int`

第一个参数是必须的，会不会导致变量覆盖漏洞由第二个参数决定，该函数有三种情况会覆盖已有变量。

例如

```php
<?php
$a = 1;    //原变量值为1
$b = array('a' => '3');
extract($b);    //经过extract()函数对$b处理后
echo $a;    //输出结果为3
?>
```

又例如

```php
<?php
$a = "0";
extract($_GET);
if ($a == 1) {
	echo "Hacked!";
} else {
	echo "Hello!";
}
?>
```

?a=1 就可以覆盖 a

例题

```php
extract($_GET);
if(isset($bdctf))
{
$content=trim(file_get_contents($flag));    //file_get_contents—将整个文件读入一个字符串
if($bdctf==$content)                        //trim—去除字符串首尾处的空白字符（或者其他字符）
{ echo'bdctf{**********}'; }
else
{ echo'这不是蓝盾的密码啊'; }
}
```

题目使用了 `extract($_GET)` 接收了 GET 请求中的数据，并将键名和键值转换为变量名和变量的值，然后再进行两个 if 的条件判断，所以可以使用 GET 提交参数和值，利用 extract() 对变量进行覆盖，从而满足各个条件。

使 $bdctf 与 $content 都为空或者不存在就满足 $bdctf==$content

get `?flag=&bdctf=` 得到 flag

***

## parse\_str()函数使用不当

`parse_str(string $string, array &$result): void`

如果未设置 array 参数，由该函数设置的变量将覆盖已存在的同名变量。

php.ini 文件中的 magic\_quotes\_gpc 设置影响该函数的输出。如果已启用，那么在 parse\_str() 解析之前，变量会被 addslashes() 转换。

parse\_str 函数的作用就是解析字符串并注册成变量，在注册变量之前不会验证当前变量是否存在，所以直接覆盖掉已有变量

例如

```php
<?php
$a = 1;                  //原变量值为1
parse_str('a=2');   //经过parse_str()函数后注册变量$a，重新赋值
print_r($a);          //输出结果为2
?>
```

例题

```php
<?php
error_reporting(0);
if(
empty($_GET['id'])) {                    //empty()检查是否为空
show_source(__FILE__);            //highlight_file—语法高亮一个文件
die();                                          //等同于exit—输出一个消息并且退出当前脚本
} else {
include (‘flag.php’);
$a = "test";
$id = $_GET['id'];
@parse_str($id);
if ($a[0] != ‘QNKCDZO’ && md5($a[0]) == md5(‘QNKCDZO’)) {
echo $flag;
} else {
exit(‘其实很简单其实并不难！’);
}
}
?>
```

PHP在处理哈希字符串时，会利用”!=”或”==”来对哈希值进行比较，它把每一个以”0E”开头的哈希值都解释为0，所以如果两个不同的密码经过哈希以后，其哈希值都是以”0E”开头的，那么PHP将会认为他们相同，都是0。

这里其实利用了弱类型的知识点

使用get请求 `?id=a[0]=s878926199a` 得到flag

***

## mb\_parse\_str()变量覆盖

mb\_parse\_str()函数用于解析GET/POST/COOKIE数据并设置全局变量，和parse\_str()类似：

```php
<?php
$a = 'oop';
mb_parse_str($_SERVER["QUERY_STRING"]);

if ($a == 'test') {
	echo "Hacked!";
} else {
	echo "Hello!";
}
?>
```

***

## register\_globals全局变量覆盖

> 注：register\_globals 已自 PHP 5.3.0 起废弃并将自 PHP 5.4.0 起移除。

php.ini 中有一项为 register\_globals，即注册全局变量，当 register\_globals=On 时，传递过来的值会被直接的注册为全局变量直接使用，而 register\_globals=Off 时，我们需要到特定的数组里去得到它。

当register\_globals=On，变量未被初始化且能够用户所控制时，就会存在变量覆盖漏洞：

```php
<?php
echo "Register_globals: " . (int)ini_get("register_globals") . "<br/>";

if ($a) {
  echo "Hacked!";
}
?>
```

通过 GET 和 POST 方式输入变量 a 的值都可以覆盖 a

从 cookie 里也可以

***

## import\_request\_variables()使用不当

> import\_request\_variables 好像比较难见到了

例如

```php
<?php
$auth='0';
import_request_variables('G');
if($auth== 1){
echo"private!";
}else{
echo"public!";
}
?>
```

get auth=1时，网页上会输出private！

import\_request\_variables('G')指定导入GET请求中的变量，从而导致变量覆盖
$$$
