php变量覆盖总结

概述

最近做了不少变量覆盖的题,总结一下。通常可以将用自定义的参数值替换原有变量的情况成为变量覆盖漏洞
根据几个函数进行判断:

经常导致变量覆盖漏洞场景有:

\$\$使用不当,
extract()函数使用不当,
parse_str()函数使用不当,
import_request_variables()使用不当,
parse_str($query)函数使用不当
开启了全局变量注册。

详解

\$\$使用不当

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

1
2
3
4
5
6
<?php
$a = 'abc';
echo $a; //输出abc
$$a = 'def';
echo $abc; //输出def
?>

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php

$test = array('a'=>'Tom','b'=>'Mary','c'=>'Peter');
foreach ($test as $key => $value) {
$$key = $value;
}
echo $a; //Tom
echo $b; //Mary
echo $c; //Peter

$a = '123';
//?a=abc
foreach ($_GET as $key => $value) {
$$key = $value;
}
echo $a; //abc
?>

漏洞例题:

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

<?php
include “flag.php”;
//$flag= i am flag
$_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); ?>

这个题的flag存在于$flag里,但是$flag在第二个foreach中会被覆盖掉,所以我们需要在第一个foreach中及时的把$flag的值转移到其他变量中去。
所以需要先将flag的值赋给_200或_403变量,然后利用die(_200)或die(_403)将flag打印出来。
GET DATA:?_200=flag
POST DATA:flag=aaaaaaaaaaaaaaaaaaaaa

extract()函数导致的变量覆盖问题

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

语法:extract(array,extract_rules,prefix)

从以上说明我们可以看到第一个参数是必须的,会不会导致变量覆盖漏洞由第二个参数决定
该函数有三种情况会覆盖已有变量。

例题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

<?php $flag = ‘xxx’;
extract($_GET);
if (isset($gift))
{
$content = trim(file_get_contents($flag));
if ($gift == $content)
{
echo ‘hctf{…}’;
}
else
{
echo ‘Oh..’;
}
} ?>

if(\$bdctf==$content) 输出flag
利用extract($\$_GET)漏洞,使$bdctf与$content都为空或者不存在就满足 \$bdctf==$content
?flag=&gift=
得到flag

parse_str()函数使用不当

parse_str() 函数把查询字符串解析到变量中。

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

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

parse_str(string,array)
参数 描述
string必需。 规定要解析的字符串。
array可选。 规定存储变量的数组名称。该参数指示变量存储到数组中。

示例:

1
2
3
4
5
6

<?php
$a = 1; //原变量值为1
parse_str('a=2'); //经过parse_str()函数后注册变量$a,重新赋值
print_r($b); //输出结果为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
31
<?php 
error_reporting(0);
include("flag.php");
$hashed_key = 'ddbafb4eb89e218701472d3f6c087fdf7119dfdd560f9d1fcbe7482b0feea05a';
$parsed = parse_url($_SERVER['REQUEST_URI']);//解析url
if(isset($parsed["query"])){
$query = $parsed["query"]; //获取url中?后的部分
$parsed_query = parse_str($query); //将字符串解析为多个变量
//$str = "first=value&arr[]=foo+bar&arr[]=baz";
//parse_str($str);
//echo $first; // value
//echo $arr[0]; // foo bar
//echo $arr[1]; // baz
if($parsed_query!=NULL){
$action = $parsed_query['action'];
}

if($action==="auth"){
$key = $_GET["key"];
$hashed_input = hash('sha256', $key); //碰撞是肯定不可能的,只能重新赋值hashed_input变量的值,使其等于hash('sha256', $key);
if($hashed_input!==$hashed_key){
die("<img src='cxk.jpg'>");
}

echo $flag;
}
}else{
show_source(__FILE__);
}?>
//?action=auth&hashed_key=a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3&key=123
//a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3是123的sha256的值

import_request_variables()使用不当

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

举例:

1
2
3
4
5
6
7
8
9
<?php
$auth='0';
import_request_variables('G');
if($auth== 1){
echo"private!";
}else{
echo"public!";
}
?>

get ?auth=1时,网页上会输出private!
import_request_variables(‘G’)指定导入GET请求中的变量,从而导致变量覆盖