变量覆盖

前言

变量覆盖指的是可以用我们自定义的参数值替换程序原有的变量值。

变量覆盖漏洞大多数由函数使用不当导致,经常引发变量覆盖漏洞的有:extract(),parse_str()和import_request_variables()函数,以及”$$”。

$$

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

1
2
3
4
5
<?php
$a = b;
$b = 2;
echo $$a; // 2
?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php
include “flag.php”;

$_403 = “Access Denied”;

$_200 = “Welcome Admin”;

if ($_SERVER["REQUEST_METHOD"] != “POST”)
{
die(“BugsBunnyCTF is here :p…”);
}
if ( !isset($_POST["flag"]) )
{
die($_403);
}
foreach ($_GET as $key => $value)
{
$$key = $$value;
}
foreach ($_POST as $key => $value)
{
$$key = $value;
}
if ( $_POST["flag"] !== $flag )
{
die($_403);
}
echo “This is your flag : “. $flag . “\n”;
die($_200);
?>

题目分析

$_SERVER[‘REQUEST_METHOD’]为访问页面时的请求方法
foreach 语法结构提供了遍历数组的简单方式。foreach 仅能够应用于数组和对象,如果尝试应用于其他数据类型的变量,或者未初始化的变量将发出错误信息。
有两种语法:

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

源码包含了flag.php文件,并且需要满足3个if里的条件才能获取flag,题目中使用了两个foreach并且也使用了$$.两个foreach中对 $$key的处理是不一样的,满足条件后会将$flag里面的值打印出来,所以$flag是在flag.php文件文件中的。

但是由于第7,11-14行间的代码会将flag的值给覆盖掉了,所以需要先将flag的值赋给_200或_403变量,然后利用die(_200)或die(_403)将flag打印出来。

解题方法

由于第7,11-14行间的代码会将$flag的值给覆盖掉,所以只能利用第一个foreach先将$flag的值赋给$_200,然后利用die($_200)将原本的flag值打印出来。

extract()函数

extract(array,extract_rules,prefix)
参数 描述
array 必需。规定要使用的数组。
extract_rules 可选。extract() 函数将检查每个键名是否为合法的变量名,同时也检查和符号表中已存在的变量名是否冲突。对不合法和冲突的键名的处理将根据此参数决定。
prefix 可选。请注意 prefix 仅在 extract_type 的值是 EXTR_PREFIX_SAME,EXTR_PREFIX_ALL,EXTR_PREFIX_INVALID 或 EXTR_PREFIX_IF_EXISTS 时需要。如果附加了前缀后的结果不是合法的变量名,将不会导入到符号表中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php 
if ($_SERVER["REQUEST_METHOD"] == “POST”)
{
extract($_POST);

if ($pass == $thepassword_123)
{

<div class=”alert alert-success”>

<code><?php echo $theflag; ?></code>

</div>

}
}
?>

题目分析

题目要求使用POST提交数据,extract($_POST)会将POST的数据中的键名和键值转换为相应的变量名和变量值,利用这个覆盖$pass和$thepassword_123变量的值,从而满足pass==thepassword_123这个条件。

解题方法

使用POST请求提交pass=1&thepassword_123=1, 然后extract()会将接收到的数据将$pass和$thepassword_123变量的值覆盖为空,便满足条件了。
PAYLOAD:pass=1&thepassword_123=1

parse_str()函数

void parse_str ( string $encoded_string [, array &$arr ] )
参数 描述
encoded_string 必需。规定要解析的字符串。
arr 可选。规定存储变量的数组的名称。该参数指示变量将被存储到数组中。
1
2
3
4
5
6
7
8
9
<?php
$name=php;
$age=100;
parse_str("name=Bill&age=60",$aa);
print_r($name); // php
print_r($age); // 100
print_r($aa[name]); // Bill
print_r($aa[age]); // 60
?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<meta charset="utf-8">
<?php
error_reporting(0);
if (empty($_GET['id'])) {
show_source(__FILE__);
die();
}
else
{
include('flag.php');
$a = "www.OPENCTF.com";
$id = $_GET['id'];
@parse_str($id);
if ($a[0] != 'QNKCDZO' && md5($a[0]) == md5('QNKCDZO')) {
echo $flag;
}
else
{
exit('其实很简单其实并不难!');
}
}
?>

题目分析

首先要求使用GET提交id参数,然后parse_str($id)对id参数的数据进行处理,再使用判断a[0] != ‘QNKCDZO’ && md5(a[0]) == md5(‘QNKCDZO’)的结果是否为真,为真就返回flag,md5(‘QNKCDZO’)的结果是0e830400451993494058024219903391。

解题方法

使用GET请求id=a[0]=240610708,这样会将a[0]的值覆盖为240610708,然后经过md5后得到0e462097431906509019562988736854与md5(‘QNKCDZO’)的结果0e830400451993494058024219903391比较都是0 所以相等,满足条件,得到flag。

import_request_variables()

bool import_request_variables ( string $types [, string $prefix ] )

将 GET/POST/Cookie 变量导入到全局作用域中。

参数 描述
types 指定需要导入的变量。可以用字母‘G’、‘P’和‘C’分别表示 GET、POST 和 Cookie。这些字母不区分大小写,所以你可以使用‘g’、‘p’和‘c’的任何组合。POST 包含了通过 POST 方法上传的文件信息。注意这些字母的顺序,当使用“gp”时,POST 变量将使用相同的名字覆盖 GET 变量。任何 GPC 以外的字母都将被忽略。
prefix 为变量名的前缀,置于所有被导入到全局作用域的变量之前。虽然 prefix 参数是可选的,但如果不指定前缀,或者指定一个空字符串作为前缀,你将获得一个 E_NOTICE 级别的错误。
1
2
3
4
5
6
7
8
<?php
if(isset($_REQUEST['btn_submit'])){
echo "正常取得的表单POST变量值:".$_REQUEST['Username']."<br />";
import_request_variables("pg", "import_");
//显示导入的变量名称
echo "使用import_request_variables函数导入的变量值:".$import_Username;
}
?>