前言

最近“深信服EDR控制端存在多个漏洞”事件让我知道了PHP变量覆盖漏洞,那就来学习总结一下。发现网上许多文章不给参考链接,这个习惯不好。

文献[1]给出了几种细分漏洞类型:

  • $$变量赋值引发的覆盖
  • extract函数导致的覆盖
  • 全局变量的覆盖
  • parse_str函数导致的覆盖
  • import_request_variables函数导致的覆盖

下面来看一下吧。目前我并没有实践,只是简单了解。后文主要是由各博文拼接修改而来的,在此感谢。

$$变量赋值引发的覆盖

$$是一种可变变量的写法,它可以使一个普通变量的值作为可变变量的名字,这种类型常常会使用遍历的方式来释放变量的代码,最常见的就是foreach的遍历,示例代码如下:

<?php
$yml = 10;
echo $yml;
echo "<br>";
foreach ($_POST as $k => $v){
    $$k = $v;
    echo $yml;
}
?>

无任何操作时正常输出,当POST内容为 yml=1000 时, $yml 的值变为了1000,成功实现变量覆盖。

extract函数导致的覆盖

该函数可以将变量从数组中导入当前的符号表。参考某教程[2]有:

将键值 “Cat”、“Dog” 和 “Horse” 赋值给变量 $a、$b 和 $c:

<?php
$a = "Original";
$my_array = array("a" => "Cat","b" => "Dog", "c" => "Horse");
extract($my_array);
echo "\\$a = $a; \\$b = $b; \\$c = $c";
?>

明白了,也就是extract可能会从不可信输入中读入恶意payload,然后覆盖重要变量。比如 [[深信服EDR控制端存在多个漏洞]] 。

这里,在extract之后又 eval($code),那么攻击者当然可以去先去覆盖变量,再去eval。

全局变量的覆盖

如果某些变量没有被初始化,并且黑客可以控制,那将会是一件很危险的事情。在这种情况下,漏洞触发的前提是 register_globalsONregister_globals 的值可以在php.ini中修改,网友发现在phpStudy上的PHP 5.2版本后该值默认是 OFF)。

例如:

<?php

echo (int)ini_get("register_globals");
echo '<br>';
echo "yml=".$yml;
?>

register_globals 打开的情况下,我们直接提交 yml=3 也会将其赋值。

还有一种情况: $GLOBALS 获取的变量在使用不当时也会导致变量覆盖,漏洞触发的前提同样是 register_globalsON 。例如,我们可以直接提交 GLOBALS[yml]=3 实现覆盖。

parse_str函数导致的覆盖

该函数可以把查询的字符串解析到变量中。参考某教程[3]有:

<?php
parse_str("name=Peter&age=43");
echo $name."<br>";
echo $age;
?>

这个函数的语法如下:

parse_str(string,array)

如果未设置 array 参数,由该函数设置的变量将覆盖已存在的同名变量。

另外,php.ini文件中的 magic_quotes_gpc 设置影响该函数的输出。如果已启用,那么在 parse_str() 解析之前,变量会被 addslashes() 转换。

所以当我们没有设置函数的第二个参数时,攻击者很可能通过特定的输入来覆盖代码中的已定义变量:

<?php
$yml = "cool";
echo "out0:".$yml;
echo "<br>";
$a = $_GET['a'];
parse_str($a);
echo "out1:".$yml;
?>

import_request_variables函数导致的覆盖

该函数将GET、POST、Cookie 变量导入到全局作用域中,如果你禁止了 register_globals,但又想用到一些全局变量,那么此函数就很有用。但该函数在最新版本的 PHP 中已经不支持。继续参考某教程[4](感谢🙏):

这个函数的语法如下:

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

实例:

<?php
// 此处将导入 GET 和 POST 变量
// 使用 runoob_ 作为前缀
import_request_variables("gP", "runoob_");

echo $runoob_foo;
?>

该函数的第二个参数用于设置注册变量的前缀。漏洞点是当第二个参数未设置时,全局变量可能会被覆盖。

例如:

<?php
$yml = "happy";
echo "out0:".$yml;
echo "<br>";
import_request_variables('P');
echo "out1:".$yml;
?>

在POST提交了 yml=xxx 时, $yml 变量将被覆盖。

参考文献

  1. https://yml-sec.top/2019/03/31/浅谈变量覆盖漏洞/
  2. https://www.runoob.com/php/func-array-extract.html
  3. https://www.runoob.com/php/func-string-parse-str.html
  4. https://www.runoob.com/php/php-import_request_variables-function.html