0x01 前言
今天在学着审cms的时候,遇到了 extract 函数.由于对变量覆盖的问题不太熟悉,故而有了这篇笔记.
0x02 变量覆盖是什么
变量覆盖指的是用我们自定义的参数值替换程序原有的变量值,一般变量覆盖漏洞需要结合程序的其它功能来实现完整的攻击。经常导致变量覆盖漏洞场景有:$$,extract()函数,parse_str()函数,import_request_variables()使用不当,开启了全局变量注册等。
0x03 全局变量覆盖
注:register_globals 从PHP 5.3.0 起废弃并将自 PHP 5.4.0 起移除。
当register_global=ON时,变量来源可能是各个不同的地方,比如页面的表单,Cookie等。
示例程序
<?php
//?id=1
echo "Register_globals: ".(int)ini_get("register_globals")."<br/>";
echo '$_GET["id"] :'.$_GET['id']."<br/>";
echo '$id :'.$id;
?>
当我们不传入ID的时候,结果如下
当我们传入ID的时候,结果如下
当我们在PHP.ini将register_globals的值设置为off的时候,这个时候就只能用$_GET[‘id’]来接收传过来的值
tips:在上面的代码中,如果已对变量进行的赋值,比如$id=0,那么即使在URL中传入id,也不会将变量覆盖.
示例代码2:
unset : 释放给定的变量,如果在函数中 unset() 一个全局变量,则只是局部变量被销毁,而在调用环境中的变量将保持调用 unset() 之前一样的值。 参考链接:https://www.php.net/unset
<?php
echo "Register_globals: ".(int)ini_get("register_globals")."<br/>";
if (ini_get('register_globals')) foreach($_REQUEST as $k=>$v) unset(${$k});
print $a."<br/>";
print $_GET[b];
?>
提交 http://192.168.0.110/test/global.php?a=1&b=2 ,变量 a 未初始化,b的值为2
提交 http://192.168.0.110/test/global.php?GLOBALS[a]=1&b=2 变量 a 的值为1,变量b的值为2.因为 unset() 不会销毁全局变量.
但是如果将代码改成如下
<?php
$b = $_GET[b];
echo "Register_globals: ".(int)ini_get("register_globals")."<br/>";
if (ini_get('register_globals')) foreach($_REQUEST as $k=>$v) unset(${$k});
print $a."<br/>";
print $b;
?>
那么现在我们提交的B值都会被销毁.
0x04 $$导致的变量覆盖问题
使用 foreach 来变量参数中的值,然后将获取到的参数名作为变量,参数的值作为变量的值.因此就产生了变量覆盖的漏洞.
示例代码如下:
<?php
foreach (array('_COOKIE','_POST','_GET') as $_request)
{
foreach ($$_request as $_key=>$_value)
{
$$_key= $_value;
}
}
$id = isset($id) ? $id : 2;
if($id == 1) {
echo "flag{xxxxxxxxxx}";
die();
}
echo $id;
?>
提交 .php?id=1">http://192.168.0.110/test/.php?id=1 ,就能显示 flag{xxxxxxxxxx} 了.
0x05 extract()变量覆盖
extract() 函数从数组中将变量导入到当前的符号表。 该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量。 第二个参数 type 用于指定当某个变量已经存在,而数组中又有同名元素时,extract() 函数如何对待这样的冲突。 该函数返回成功导入到符号表中的变量数目。
示例代码如下
<?php
extract($_GET);
echo $name.'<br>';
echo $age.'<br>';
echo $phone.'<br>';
//GET传参:?name=xiaohua&age=22&phone=112323123
//结果:
// xiaohua
// 22
// 112323123
?>
tips: 在调用extract()时使用EXTR_SKIP保证已有变量不会被覆盖 extract($_GET,EXTR_SKIP);
0x06 parse_str()变量覆盖
parse_str() 函数把查询字符串解析到变量中。 注释:如果未设置 array 参数,则由该函数设置的变量将覆盖已存在的同名变量。 注释:php.ini 文件中的 magic_quotes_gpc 设置影响该函数的输出。如果已启用,那么在 parse_str() 解析之前,变量会被 addslashes() 转换。
示例代码:
<?php
$var='init';
parse_str($_SERVER['QUERY_STRING']);
print $var;
?>
// GET传参:?var=1
// 结果
?? 1
tip:与parse_str()类似的函数还有mb_parse_str().如果参数str是URL传递入的查询字符串(query string),则将它解析为变量并设置到当前作用域。
0x07 import_request_variables变量覆盖
import_request_variables() 函数将 GET/POST/Cookie 变量导入到全局作用域中。该函数在最新版本的 PHP 中已经不支持。 版本要求:PHP 4 >= 4.1.0, PHP 5 < 5.4.0 bool import_request_variables ( string $types [, string $prefix ] ) $types:指定需要导入的变量,可以用字母 G、P 和 C 分别表示 GET、POST 和 Cookie,这些字母不区分大小写,所以你可以使用 g 、 p 和 c 的任何组合。POST 包含了通过 POST 方法上传的文件信息。注意这些字母的顺序,当使用 gp 时,POST 变量将使用相同的名字覆盖 GET 变量。任何 GPC 以外的字母都将被忽略。
示例代码:
<?php
import_request_variables("g", "get_");
echo $get_id;
?>
// 提交:?id=111
// 结果:111
0x08 参考链接
https://www.cnblogs.com/xiaozi/p/7768580.html
https://www.cnblogs.com/drkang/p/8689205.html