• PHP代码审计14—变量覆盖


    一、PHP变量覆盖基础

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

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

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

    漏洞简介:

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

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

    漏洞示例:

    
    $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会直接将传入的参数定义为全局变量,导致变量覆盖。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2、$$动态变量覆盖

    基本概念:

    PHP动态变量是指一个变量名的变量名可以动态的设置和使用,一个变量获取另一个变量的值作为这个变量的变量名。
    
    • 1

    示例代码:

    
    $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
    ?>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    3、extract()函数变量覆盖

    函数详解:

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

    示例代码:

    
    $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; 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    4、parse_str函数变量覆盖

    函数详解:

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

    示例代码:

    
    parse_str("a=this&b=is&c=flag");
    echo $a." ";
    echo $b." ";
    echo $c;
    ?>
    //this is flag
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    5、import_request_variables()变量覆盖

    函数解析:

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

    示例代码:

    
    $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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    二、CTF例题分析

    例题一

    源码:

    
    $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}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    思路分析:

    这里发现存在 $$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,"
    "
    ; var_dump($a); echo "
    "
    ; if ($a[0] != 'QNKCDZO' && md5($a[0]) == md5('QNKCDZO')) { echo $flag; }else{ exit('你的答案不对0.0'); } } ?>
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    可以发现,我们需要满足$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

    测试结果如下:

    在这里插入图片描述

    例题三

    源码:

    
    $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';
        }
    }
    ?>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    简单分析:

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

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

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

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

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

    isset($shiyan) //true
    $shiyan==$content //true
    
    • 1
    • 2

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

    测试结果:

    在这里插入图片描述

    三、参考链接

  • 相关阅读:
    模拟滤波器和经典的低通滤波器
    详解升讯威在线客服系统前端多国语言实现技术:原生支持葡文、印尼文、土耳其文、俄文
    Mysql词法分析实验(一)
    c++入门99题第1-10
    时序预测 | MATLAB实现ICEEMDAN-IMPA-GRU时间序列预测
    Java当中的栈
    R语言ggplot2可视化:使用ggpubr包的ggbarplot函数可视化柱状图、使用order参数自定义柱状图中水平的顺序
    React知识点系列(7)-每天10个小知识
    2、并发三大特性与JMM详解
    多视角碰撞,探索 Serverless 企业落地更多可能性丨阿里云用户组厦门站
  • 原文地址:https://blog.csdn.net/qq_45590334/article/details/126306461