什么是变量覆盖?
变量覆盖,顾名思义,用我们自己构造的变量覆盖原本已经有的变量的值,这样我们就可以控制两个变量比较的结果。同时也借这篇文章来记录一下我的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