0x01 前言

今天在学着审cms的时候,遇到了 extract 函数.由于对变量覆盖的问题不太熟悉,故而有了这篇笔记.

0x02 变量覆盖是什么

变量覆盖指的是用我们自定义的参数值替换程序原有的变量值,一般变量覆盖漏洞需要结合程序的其它功能来实现完整的攻击。经常导致变量覆盖漏洞场景有:$$,extract()函数,parse_str()函数,import_request_variables()使用不当,开启了全局变量注册等。

0x03 全局变量覆盖

注:register_globals 从PHP 5.3.0 起废弃并将自 PHP 5.4.0 起移除。

当register_global=ON时,变量来源可能是各个不同的地方,比如页面的表单,Cookie等。
示例程序

  1. <?php
  2. //?id=1
  3. echo "Register_globals: ".(int)ini_get("register_globals")."<br/>";
  4. echo '$_GET["id"] :'.$_GET['id']."<br/>";
  5. echo '$id :'.$id;
  6. ?>

当我们不传入ID的时候,结果如下
image.png
当我们传入ID的时候,结果如下
image.png
当我们在PHP.ini将register_globals的值设置为off的时候,这个时候就只能用$_GET[‘id’]来接收传过来的值
tips:在上面的代码中,如果已对变量进行的赋值,比如$id=0,那么即使在URL中传入id,也不会将变量覆盖.

示例代码2:

unset : 释放给定的变量,如果在函数中 unset() 一个全局变量,则只是局部变量被销毁,而在调用环境中的变量将保持调用 unset() 之前一样的值。 参考链接:https://www.php.net/unset

<?php  
echo "Register_globals: ".(int)ini_get("register_globals")."<br/>"; 
if (ini_get('register_globals')) foreach($_REQUEST as $k=>$v) unset(${$k});  
print $a."<br/>";  
print $_GET[b];  
?>

提交 http://192.168.0.110/test/global.php?a=1&b=2 ,变量 a 未初始化,b的值为2
提交 http://192.168.0.110/test/global.php?GLOBALS[a]=1&b=2 变量 a 的值为1,变量b的值为2.因为 unset() 不会销毁全局变量.
但是如果将代码改成如下

<?php  
$b = $_GET[b];
echo "Register_globals: ".(int)ini_get("register_globals")."<br/>"; 
if (ini_get('register_globals')) foreach($_REQUEST as $k=>$v) unset(${$k});  
print $a."<br/>";  
print $b;  
?>

那么现在我们提交的B值都会被销毁.

0x04 $$导致的变量覆盖问题

使用 foreach 来变量参数中的值,然后将获取到的参数名作为变量,参数的值作为变量的值.因此就产生了变量覆盖的漏洞.
示例代码如下:

<?php
foreach (array('_COOKIE','_POST','_GET') as $_request)  
{
    foreach ($$_request as $_key=>$_value)  
    {
        $$_key=  $_value;
    }
}
$id = isset($id) ? $id : 2;
if($id == 1) {
    echo "flag{xxxxxxxxxx}";
    die();
}
echo $id;
?>

提交 .php?id=1">http://192.168.0.110/test/.php?id=1 ,就能显示 flag{xxxxxxxxxx} 了.

0x05 extract()变量覆盖

extract() 函数从数组中将变量导入到当前的符号表。 该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量。 第二个参数 type 用于指定当某个变量已经存在,而数组中又有同名元素时,extract() 函数如何对待这样的冲突。 该函数返回成功导入到符号表中的变量数目。

示例代码如下

<?php
extract($_GET);  
echo $name.'<br>';
echo $age.'<br>';
echo $phone.'<br>';

//GET传参:?name=xiaohua&age=22&phone=112323123

//结果:
// xiaohua
// 22
// 112323123
?>

tips: 在调用extract()时使用EXTR_SKIP保证已有变量不会被覆盖 extract($_GET,EXTR_SKIP);

0x06 parse_str()变量覆盖

parse_str() 函数把查询字符串解析到变量中。 注释:如果未设置 array 参数,则由该函数设置的变量将覆盖已存在的同名变量。 注释:php.ini 文件中的 magic_quotes_gpc 设置影响该函数的输出。如果已启用,那么在 parse_str() 解析之前,变量会被 addslashes() 转换。

示例代码:

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

// GET传参:?var=1
// 结果
?? 1

tip:与parse_str()类似的函数还有mb_parse_str().如果参数str是URL传递入的查询字符串(query string),则将它解析为变量并设置到当前作用域。

0x07 import_request_variables变量覆盖

import_request_variables() 函数将 GET/POST/Cookie 变量导入到全局作用域中。该函数在最新版本的 PHP 中已经不支持。 版本要求:PHP 4 >= 4.1.0, PHP 5 < 5.4.0 bool import_request_variables ( string $types [, string $prefix ] ) $types:指定需要导入的变量,可以用字母 G、P 和 C 分别表示 GET、POST 和 Cookie,这些字母不区分大小写,所以你可以使用 g 、 p 和 c 的任何组合。POST 包含了通过 POST 方法上传的文件信息。注意这些字母的顺序,当使用 gp 时,POST 变量将使用相同的名字覆盖 GET 变量。任何 GPC 以外的字母都将被忽略。

示例代码:

<?php
import_request_variables("g", "get_");
echo $get_id;
?>
// 提交:?id=111
// 结果:111

0x08 参考链接

https://www.cnblogs.com/xiaozi/p/7768580.html
https://www.cnblogs.com/drkang/p/8689205.html