前言
护网面试被问到是否了解到php中哪些函数或者因素会导致变量覆盖漏洞,当时回答的不完全,今天就 复习了一下这个知识点,然后顺手做下个人的笔记,希望能帮助到大家一起进步
变量覆盖漏洞介绍
变量覆盖就是通过外部输入将某个变量的值给覆盖掉。 将自定义的参数值替换掉原有变量值的情况就是变量覆盖漏洞。
变量覆盖的函数
常见的可导致变量覆盖的函数或者因素有:
● register_globals
● extract()
● parse_str()
● mb_parse_str()
● import_request_variables()
● $$
1、register_globals
register_globals是php.ini里的一个配置,这个配置影响到php如何接收传递过来的参数.
register_globals的值可以设置为:On或者Off
当register_globals的值设置为ON时,传递过来的值会被直接注册为全局变量而使用,这会造成全局变量覆盖
在PHP5.3之前 默认开启 PHP5.3默认关闭 PHP5.6及5.7已经被移除
测试环境:php-5.2.17+Aapache
<?php
$taco=1;
if($taco=2)
{
echo "flag{yes}";
}
?>
$taco==2时就可以输出flag{yes},但是在前面定义变量$taco时的值时1,想要输出flag,就要修改$taco的值为2
因为register_globals的值设置为ON,所以可以利用全局变量覆盖来修改$taco的值,在访问该文件时,GET方式传给$taco=2,就可达到变量覆盖
2、extract()函数变量覆盖
PHP extract()
函数用于将数组中的变量导入到当前的符号表中。 它需要一个关联数组数组,并将键作为变量名和值作为变量值。
对于每个键/值对,它将在当前符号表中创建一个变量,受到extract_type
和prefix
参数的限制。
语法:
extract($array, $extract_type, $prefix)
例如:
<?php
extract($_GET);
echo $name.'<br>';
echo $age.'<br>';
echo $id.'<br>';
?>
当以GET方式传入?name=tacoking&age=21&id=123456时,输出的结果就是$name=tacoking;$age=22;$id=123456
这是name、age、id作为变量名,传进来的值作为了它们的变量值
CTF中extract()导致的变量覆盖
<?php
$flag='flag.php';
extract($_GET);
if(isset($test1))
{
$test2=file_get_contents($flag);
if($test1==$test2)
{
echo'flag{yes}';
}
else
{
die();
}
}
?>
分析是要求GET传参进去值会经过extract()函数 ,然后有两个if 第一个if判断$test1这个变量是否存在 ,存在则继续执行第二个if里面的
使用file_get_contents()读取flag变量里面的文件的内容传递给$test2变量 ,之后 再进行判断传进来$test1变量的值等不等于$test2如果等于则打印出flag
因为通过extract()函数传进的值会变成一个变量 例如GET传入?test1=0 则会存在$test1=0
payload:
?test1=&flag=
3、parse_str()变量覆盖
定义和用法
parse_str() 函数把查询字符串解析到变量中。
注释:如果未设置 array 参数,由该函数设置的变量将覆盖已存在的同名变量。
注释:php.ini 文件中的 magic_quotes_gpc 设置影响该函数的输出。如果已启用,那么在 parse_str() 解析之前,变量会被 addslashes() 转换。
语法:
parse_str(*string,array*)
请看下面的两段代码分析:
<?php
$name="lihua";
$age=18;
echo $name;
echo $age;"<br>";
?>
这段代码很简单明显输出的是name=lihua,age=18
但是下面那个添加了parse_str()函数后,输出的就是name=tacoking,age=21了
是因为parse_str()函数里未设置 array 参数,由该函数设置的变量将覆盖已存在的同名变量,所以之前变量name跟age的值就被覆盖掉了
<?php
header("Content-Type:text/html;charset=UTF-8");
$name="lihua";
$age=18;
echo "覆盖前name=".$name."<br>";
echo "覆盖前age=".$age."<br>";
echo "<br>";
parse_str("name=tacoking&age=21");
echo "覆盖后name=".$name."<br>";
echo "覆盖后age=".$age;
?>
CTF中parse_str()导致的变量覆盖
<?php
error_reporting(0);
$flag=$_GET['flag'];
//flag in flag.php
if (empty($_GET['id'])) {
show_source(__FILE__);
die();
} else {
$a = "tacoking";
$id = $_GET['id'];
@parse_str($id); //
if ($a[0] != 'QNKCDZO' && md5($a[0]) == md5('QNKCDZO')) {
include($flag);
} else {
exit("no no");
}
}
?>
分析代码 判断GET传入的id值是否为空,为空的话输出源码终止程序,否则的话则接收id值 经过parse_str(), 然后if判断 $a[0] 的值要不等于QNKCDZO,而且$a[0]的md5值要等于QNKCDZO,如果条件满足的话就会包含flag.php
思路:
$a[0] != 'QNKCDZO' && md5($a[0]) == md5('QNKCDZO') 这里可以采用Hash比较缺陷来解决
include($flag)的话可以利用php伪协议读取flag内容
接 下来就是传入变量覆盖$a[0]的值 ,因为有parse_str()函数
所以可以构造payload:
?id=a[0]=s878926199a&flag=php://filter/read=convert.base64-encode/resource=flag.php
ZmxhZ3t5ZXN9经过base64解码后flag{yes},成功读取到flag
4、mb_parse_str()
mb_parse_str()PHP 中的函数用于解析 GET、POST 和 COOKIE 数据并设置全局变量。它解析 URL 编码的数据并检测编码。之后,它转换内部编码中的编码并设置全局变量的值。PHP 7 或更高版本支持此功能。
语法:
string mb_parse_str($str_string, $array_result)
参数:
mb_parse_str() 接受以下两个参数 -
-
$str_string - 此参数用于 URL 编码数据。
-
$result -结果参数将是一个包含解密和字符加密转换值的数组。
返回值:
该mb_parse_str()函数在成功时返回 True 或在失败时返回 False。如果解析成功,则返回True,否则返回False。
mb_parse_str()函数和parse_str()类似,此不做累赘,感兴趣的可自行研究。
5.、import_request_variables()
(PHP 4 >= 4.1.0, PHP 5 < 5.4.0)
mport_request_variables — 将 GET/POST/Cookie 变量导入到全局作用域中
将 GET/POST/Cookie 变量导入到全局作用域中。如果禁止了 register_globals,但又想用到一些全局变量,那么此函数就很有用。
语法:
import_request_variables ( string $types [, string $prefix ] ) : bool
CTF中import_request_variables()导致的变量覆盖
<?php
$str='test';
$flag=$_POST['flag'];
//flag in flag.php
import_request_variables('gp'); //导入get和post中变量
if($str=="tacoking"){
include $flag;
}else{
echo "NO!";
}
?>
定义一个变量$str='test',$flag等于post传进来的值,然后设置import_request_variables('gp'),导入get和post中变量,如果str==tacoking的话就会包含$flag,这里可以使用php伪协议来读取flag.php内容
payload:
GET传:str=tacoking POST传:flag=php://filter/read=convert.base64-encode/resource=flag.php
ZmxhZ3t5ZXN9经过base64解码后flag{yes},成功读取到flag
6、$$动态变量覆盖
PHP动态变量是指一个变量名的变量名可以动态的设置和使用,一个变量获取另一个变量的值作为这个变量的变量名。
如:
<?php
$bar= "a";
$Foo="Bar";
$World="Foo";
$Hello="world";
$a="Hello";
echo $a; //hello
echo $$a; //world
echo $$$a; //foo
echo $$$$$a; //Bar
echo $$$$$$a; //a
echo $$$$$$$a; //hello
echo $$$$$$$$a; //world
?>
CTF中$$导致的变量覆盖
$$ 是最经典的变量覆盖,导致的变量覆盖问题在CTF代码审计题目中经常在foreach中出现
下面的题解引用了其他师傅的文章
https://www.cnblogs.com/xhds/p/12586928.html
如以下的示例代码,使用foreach来遍历数组中的值,然后再将获取到的数组键名作为变量,数组中的键值作为变量的值。
因此就产生了变量覆盖漏洞。请求?name=test 会将$name的值覆盖,变为test。
<?php
$name='thinking';
foreach ($_GET as $key => $value)
$$key = $value; //这里进行了覆盖 $$key传入的值是name 传入进入成为$name 所以造成了name外部的变量被覆盖
var_dump($key);
var_dump($value);
var_dump($$key);
echo $name;
//?name=test
//output:string(4) “name”
//string(4) “test”
//string(4) “test”
//test