变量覆盖

 Von's Blog     2019-09-12   3691 words    & views

什么是变量覆盖?

变量覆盖,顾名思义,用我们自己构造的变量覆盖原本已经有的变量的值,这样我们就可以控制两个变量比较的结果。同时也借这篇文章来记录一下我的PHP入门,所以文中很多东西可能会讲得比较啰嗦,请大佬见谅。

全局变量覆盖

全局变量注册,本特性已自 PHP 5.3.0 起废弃并将自 PHP 5.4.0 起移除。(所以有点鸡肋。。。)
当register_global=ON时,变量来源可能是各个不同的地方,比如页面的表单,Cookie等。

例子

<?php
echo "Register_globals: " . (int)ini_get("register_globals") . "<br/>";
if ($auth) {
    echo "覆盖!";
}else{
    echo "没有覆盖";
}

当访问http://127.0.0.1/1.php时输出没有覆盖
但是当请求http://127.0.0.1/1.php?auth=1时会覆盖掉$auth输出覆盖。

$$类型

前置知识

$$这种写法称为可变变量,一个可变变量获取了一个普通变量的值作为这个可变变量的变量名。

<?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仅能用于数组,每次循环中,当前单元的键名也会在每次循环中被赋给变量$key。当前单元的值被赋给$value并且数组内部的指针向前移一步。

foreach (array_expression as $value)
    statement
foreach (array_expression as $key => $value)
    statement

第一种格式遍历给定的array_expression数组.每次循环中,当前单元的值被赋给$value并且数组内部的指针向前移一步(因此下一次循环中将会得到下一个单元).
第二种格式做同样的事,只除了当前单元的键名也会在每次循环中被赋给变量$key.

例子

题目链接 查看源码:

<!--foreach($_GET as $key => $value){  
        $$key = $value;  
}  
if($name == "meizijiu233"){
    echo $flag;
}-->

当我们传入?name=meizijiu233时,会有$key=name,$value=meizijiu233,即有\(key=$name=meizijiu233,满足if语句,从而输出flag. 小记:在代码审计时需要注意类似“\)k”的变量赋值方式有可能覆盖已有的变量,从而导致一些不可控制的结果。

extract()变量覆盖

前置知识

extract() 函数从数组中将变量导入到当前的符号表。
该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量。
代码示例:将键值 “Cat”、”Dog” 和 “Horse” 赋值给变量 $a、$b 和 $c:

<?php
$a = "Original";
$my_array = array("a" => "Cat","b" => "Dog", "c" => "Horse");
extract($my_array);
echo "\$a = $a; \$b = $b; \$c = $c";
?>
//运行结果:$a = Cat; $b = Dog; $c = Horse

extract(array,extract_rules,prefix) 第二个参数 type 用于指定当某个变量已经存在,而数组中又有同名元素时,extract() 函数如何对待这样的冲突。
最常见的两个值是EXTR_OVERWRITE 和EXTR_SKIP。 当值为 EXTR_OVERWRITE 时,在将变量导入符号表的过程中,如果变量名发生冲突,则覆盖所有变量;值为EXTR_SKIP 则表示跳过不覆盖。若第二个参数未指定,则在默认情况下使用EXTR_OVERWRITE。

例子

题目链接
查看源代码:

<?php if ($_SERVER["REQUEST_METHOD"] == "POST") { ?>
                        <?php
                        extract($_POST);
                        if ($pass == $thepassword_123) { ?>
                            <div class="alert alert-success">
                                <code><?php echo $theflag; ?></code>
                            </div>
                        <?php } ?>
                    <?php } ?>

当我们传入pass=&&thepassword_123=时,由于extract()函数,导致pass和thepassword_123都变成空。从而达到了绕过的目的。

parse_str()变量覆盖

前置知识

parse_str()函数把查询字符串解析到变量中。
用法:parse_str(string,array)

<?php
parse_str("name=Bill&age=60");
echo $name."<br>";   //输出Bill
echo $age;           //输出60
?>

若有第二个参数,那么会把第一个参数中的值放到第二个参数数组中,如果数组中有键名相同的值,也会进行覆盖。
若没有第二个数组参数,如果变量重复,就会覆盖原来的变量。

例子

<?php
error_reporting(0);
if (empty($_GET['id'])) {
    show_source(__FILE__);
    die();
}
 else {
    $a = "www.OPENCTF.com";
    $id = $_GET['id'];
    @parse_str($id);
    if ($a[0] != "QNKCDZO" && md5($a[0]) == md5("QNKCDZO")) {
        echo "ok";
    } else {
        exit("其实很简单其实并不难!");
    }
}
?>

得出flag的i条件是满足php弱比较,可以发现pharse_str函数并没有第二个数组参数,那么是存在变量覆盖的,*我们只需要通过get的方式将a[0] 的值修改成满足题意的即可。
故payload:?id=a[0]=240610708

import_request_variables变量覆盖

前置知识

import_request_variables—将 GET/POST/Cookie 变量导入到全局作用域中 import_request_variables()函数就是把GET、POST、COOKIE的参数注册成变量,用在register_globals被禁止的时候.
语法:bool import_request_variables(string$types[,string$prefix] )
$type代表要注册的变量,G代表GET,P代表POST,C代表COOKIE,第二个参数为要注册变量的前缀。

例子

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

当构造payload:?auth=1时会导致变量覆盖,输出private。

参考文章

panda1g1师傅的文章
https://y4er.com/post/variable-coverage/#import-request-variables
https://p0sec.net/index.php/archives/35/
https://www.jianshu.com/p/a4d782e91852