PHP变量覆盖

1 register_global

全局变量注册,本特性已自 PHP 5.3.0 起废弃并将自 PHP 5.4.0 起移除

当在php.ini开启register_globals= On时,代码中的参数会被用户提交的参数覆盖掉。

1
2
3
4
5
6
<?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输出覆盖

2 extract()

从数组中将变量导入到当前的符号表 直接看代码

1
2
3
4
5
6
7
<?php
$auth=false;
extract($_GET);

if ($auth){
echo "over";
}

同样请求http://127.0.0.1/1.php?auth=1时会覆盖掉$auth输出over

3 $$

$$符号在php中叫做可变变量,可以使变量名动态设置。举个例子

1
2
3
4
<?php
$a='hello';
$b='a';
$$b='world' //$a = 'world'

这里$$b = $a,$$b就将 $a 中的值改掉了

1
2
3
4
5
$auth=0;
foreach ($_GET as $key => $value) {
$$key=$value;
}
echo $auth;

在第二行中遍历了全局变量$_GET,第三行将key当作变量名,把value赋值。 那么我们传入http://127.0.0.1/1.php?auth=1时会将$auth的值覆盖为1

4 import_request_variables

将 GET/POST/Cookie 变量导入到全局作用域中,如果你禁止了 register_globals,但又想用到一些全局变量,那么此函数就很有用。那么和register_globals存在相同的变量覆盖问题。

1
2
3
4
5
6
$auth = '0';
import_request_variables('G');

if($auth == 1){
echo "over!";
}

同样传入http://127.0.0.1/1.php?auth=1时会将$auth的值覆盖为1,输出over!

5 parse_str()

将字符串解析成多个变量

1
2
3
4
$a='aa';
$str = "a=test";
parse_str($str);
echo $a; //输出:test

可以看出来将$str解析为$a='test',与parse_str()类似的函数还有mb_parse_str(),不在赘述。

来看ctf中的一道题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php 
error_reporting(0);
include("flag.php");
$hashed_key = 'ddbafb4eb89e218701472d3f6c087fdf7119dfdd560f9d1fcbe7482b0feea05a';
$parsed = parse_url($_SERVER['REQUEST_URI']);
if(isset($parsed["query"])){
$query = $parsed["query"];
$parsed_query = parse_str($query);
if($parsed_query!=NULL){
$action = $parsed_query['action'];
}

if($action==="auth"){
$key = $_GET["key"];
$hashed_input = hash('sha256', $key);
if($hashed_input!==$hashed_key){
die("no");
}

echo $flag;
}
}else{
show_source(__FILE__);
}?>

在第五行将请求的URI通过parse_url()解析后赋值给$parsed变量。

在第八行将我们提交的query参数使用parse_str解析,这时就产生了变量覆盖的问题,我们可以通过query提交参数去覆盖变量。

接着往下看$hashed_input!==$hashed_key成立输出flag,此时$hashed_input我们可控,那么我们可以覆盖掉$hashed_key来使条件成立

$hashed_input = hash('sha256', $key);是sha256加密,那么我们可以传入key=a,此时$hashed_input等于ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb

然后覆盖$hashed_key,传入query=&hashed_key=ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb覆盖掉。

这时payload为

1
http://39.100.83.188:8066/?query=&hashed_key=ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb&action=auth&key=a

可能有人会疑惑参数hashed_key前为什么要加&

  • 如果直接 ?query=hashed_key=...,那么 parse_str("hashed_key=...") 只会设置 $hashed_key

  • 但我们需要同时设置 $action$key 变量

  • 所以需要:?query=&hashed_key=...&action=auth&key=a

    • 开头的 & 确保 hashed_key 被正确解析为一个独立的参数
    • 后面的 &action=auth&key=a 设置其他必要变量