• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

PHP代码审计14—变量覆盖

武飞扬头像
W0ngk
帮助5

一、PHP变量覆盖基础

变量覆盖漏洞的基础概念:

变量覆盖漏洞是指自定义的参数替换原有变量值的情况,如$$使用不当,extract函数使用不当,parse_str() 函数使用不当,import_request_variables() 使用不当,开启了全局变量注册等。

1、全局变量导致的变量覆盖

漏洞简介:

当register_globals全局变量设置开启时,传递过来的值会被直接注册为全局变量而使用,这会造成全局变量覆盖 。

register_globals全局变量设置, 在PHP5.3之前默认开启, PHP5.3默认关闭 ,PHP5.6以后已经被移除 。

漏洞示例:

<?php
$num=0;
if ($num){
 echo "flag{this is flag}";
}
?> 
//payload: http://xx.xx.xx.xx/test.php?num=1
//result: flag{this is flag}
//解析:由于我们使用的是低版本的PHP,默认开启了全局变量,当我们传入num的时候,PHP会直接将传入的参数定义为全局变量,导致变量覆盖。

2、$$动态变量覆盖

基本概念:

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
?>

3、extract()函数变量覆盖

函数详解:

定义:extract() 函数从数组中将变量导入到当前的符号表。该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量,返回成功设置的变量数目。
语法:extract(array,extract_rules,prefix)
	//array:必须,规定用于准换的数组
  //extract_rules:可选,函数将检查每个键名是否为合法的变量名,同时也检查和符号表中已存在的变量名是否冲突。对不合法和冲突的键名的处理将根据此参数决定。默认设置EXTR_OVERWRITE,有冲突则覆盖。
  //prefix:该参数规定了前缀。前缀和数组键名之间会自动加上一个下划线,可选。

示例代码:

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

4、parse_str函数变量覆盖

函数详解:

语法:parse_str(string,array)
定义:parse_str() 函数把查询字符串解析到变量中。如果未设置 array 参数,由该函数设置的变量将覆盖已存在的同名变量。
注释:php.ini 文件中的 magic_quotes_gpc 设置影响该函数的输出。如果已启用,那么在 parse_str() 解析之前,变量会被 addslashes() 转换。

示例代码:

<?php
parse_str("a=this&b=is&c=flag");
echo $a." ";
echo $b." ";
echo $c;
?>
//this is flag

5、import_request_variables()变量覆盖

函数解析:

使用范围:PHP 4 >= 4.1.0, PHP 5 < 5.4.0
函数语法:import_request_variables ( string $types [, string $prefix ] )
作用:将 GET/POST/Cookie 变量导入到全局作用域中。如果你禁止了 register_globals,但又想用到一些全局变量,那么此函数就很有用。
参数说明:
	$types:指定需要导入的变量,可以用字母 G、P 和 C 分别表示 GET、POST 和 Cookie,这些字母不区分大小写
	$prefix:变量名的前缀,置于所有被导入到全局作用域的变量之前。所以如果你有个名为 userid 的 GET 变量,同时提供了 pref_ 作为前缀,那么你将获得一个名为 $pref_userid 的全局变量。虽然 prefix 参数是可选的,但如果不指定前缀,或者指定一个空字符串作为前缀,你将获得一个 E_NOTICE 级别的错误。
	

示例代码:

<?php
$num=0;
$flag="this is flag";
import_request_variables('gp'); //导入get和post中变量
if($num){
 echo $flag;
}else{
 echo "NO!";
}
?> 
//payload:http://127.0.0.1/test.php?num=xiaohua
//result: this is flag

二、CTF例题分析

例题一

源码:

<?php
$flag="flag{34nt_8tuhg_INF_49nfe}";
$yds = "dog";
$is = "cat";
$handsome = 'yds';
foreach($_POST as $x => $y){  //遍历POST中的数据,然后将$value赋值给$$key。
     $$x = $y; 
}
foreach($_GET as $x => $y){  //循环遍历GET中的数据,将$$value赋值给$$key
    $$x = $$y;
}
foreach($_GET as $x => $y){
     if($_GET['flag'] === $x && $x !== 'flag'){ //循环遍历GET参数,如果设置flag参数,并且参数值不等于flag,输出$handsome
        exit($handsome);
     }
}  
if(!isset($_GET['flag']) && !isset($_POST['flag'])){  //POST和GET都不设置flag参数,输出$yds
     exit($yds);
}
if($_POST['flag'] === 'flag'  || $_GET['flag'] === 'flag'){ //post或者get设置了flag参数,输出$is
     exit($is);
}
echo "the flag is: ".$flag;

//payload: test.php?yds=flag
//result: flag{34nt_8tuhg_INF_49nfe}
学新通

思路分析:

这里发现存在 $$x = $$y;所以肯定是需要进行变量覆盖利用的,如果我们想要在最后echo的时候,输出flag,就需要绕过前面的三个检测。

首先如果设置了flag参数,则会在第2个foreach中,使我们的$flag=$$value,这里如果构造$value=flag,那么在第三个foreach中,进入if判断,输出$handsome。所以无法利用。

如果我们在POST或者GET都不设置flag参数,在第二个foreach中,就会将$$key赋值为$$value,然后进入if(!isset($_GET['flag']) && !isset($_POST['flag'])) 输出$yds.如果此时我们传入?yds=flag,就会让$yds=$falg,成功在exit处输出。

如果我们试图进入最后一个if,进行判断,首先设置GET参数flag=flag的话,在第二个foreach中会设置$flag=$flag,并没有什么影响,所以需要加上POST参数,这里如果设置POST参数is=$flag的话,会输出$falg,导致flag变量变成了$flag字符串,无法获取正常的flag。

如果在POST中设置flag=flag的话,就会导致$flag=flag,也不可行,所以就只有上面一个方法能行得通。

例题二

源码:

<meta charset="utf-8">
<?php
error_reporting(0);
if (empty($_GET['b'])) {
    show_source(__FILE__);
    die();
}else{
    $flag = "flag{This is flag!!!}";
    $a = "www.XMAN.com";
    $b = $_GET['b'];
    parse_str($b);
    echo $b,"<br/>";
    var_dump($a);
    echo "<br/>";
    if ($a[0] != 'QNKCDZO' && md5($a[0]) == md5('QNKCDZO')) {
        echo $flag;
    }else{
        exit('你的答案不对0.0');
    }
}
?>
学新通

可以发现,我们需要满足$a[0] != 'QNKCDZO' && md5($a[0]) == md5('QNKCDZO')才能输出flag。可以知道这里需要使用PHP弱类型绕过md5检测,但是$a已经国定,所以就需要想办法改变$a的值。

由于上面使用了 parse_str()函数,所以就可以尝试使用变量覆盖来改变$a的值。

绕过MD5检测,可以让$a[0]等于任意的弱碰撞即可,比如s1502113478a。然后利用 parse_str()进行变量覆盖的话,我们可以传入?b=a[]=s1502113478a,这样就能成功的覆盖$a.

所以这里的paylaod就是:test.php?b=a[]=s1502113478a

测试结果如下:

学新通

例题三

源码:

<?php
$flag='dog.txt';
extract($_GET);
if(isset($shiyan)){
    $content=trim(file_get_contents($flag));
    if($shiyan==$content){
    		echo 'flag{this is  flag,your are right!}';
    }else {
    		echo'Oh.no';
    }
}
?>

简单分析:

首先使用了extrct()来导入GET中的变量。

如果设置了$shiyan参数,则进入if语句。

使用file_get_contents()获取$flag对应的文件的内容,并去除前后的空格。

如果$shiyan的值等于文件内容,则输出flag。

所以这里需要解决的啷个问题就是:

isset($shiyan) //true
$shiyan==$content //true

对于第一个问题,我们只需要传入一个名为shiyan的参数即可,对于第二个问题,由于这里使用了==,所以存在弱类型的问题,当我们的文件不存在或读取失败的时候,会返回空,所以,只要我们传入的$shiyan为空即可绕过。

测试结果:

学新通

三、参考链接

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhfiabii
系列文章
更多 icon
同类精品
更多 icon
继续加载