PHP变量覆盖

    xiaoxiao2022-07-13  153

    前言

    变量覆盖指的是用我们自定义的参数值替换程序原有的变量值,一般变量覆盖漏洞需要结合程序的其它功能来实现完整的攻击。

    经常导致变量覆盖漏洞场景有:$$,extract()函数,parse_str()函数等

    正文

    **$**变量覆盖问题

    简介

    在PHP中$$表示的是一个可变变量获取了一个普通变量的值作为这个可变变量的变量名。

    <?php $c='hello'; $$c='world'; echo $c; echo $$c; ?>

    在这个例子中, 是 变 量 标 识 符 , c 是 变 量 名 , 而 下 面 的 变 量 是变量标识符,c是变量名,而下面的变量 c c , 是 把 c,是把 cc当成了当成了变量名的一个变量,上面的输出结果是:

    helloworld //$©=hello ( ( (c)=world 这样看起来更好一点

    漏洞产生

    使用foreach来遍历数组中的值,然后再将获取到的数组键名作为变量,数组中的键值作为变量的值。因此就产生了变量覆盖漏洞。

    <?php $ary=array('a','b','c','c','e'); foreach($ary as $key=>$value){ //$ary的键名赋给$key,键值赋给$value $$key=$value; //把键值赋给$$key } print_r($key); //输出4 print_r($value); //输出e print_r($$key); //输出e ?>

    换成 _ G E T 和 \_GET和 _GET_POST是一样的

    <?php $id=5; foreach ($_GET as $key => $value) { $$key = $value; } echo $a; ?>

    如果get传的是?id=1,那么,经过**$$key = v a l u e ∗ ∗ 之 后 , 就 会 变 成 ‘ value**之后,就会变成` valueid=1,覆盖掉原来的$id=5`.

    漏洞复现
    <?php include "flag.php"; $_403 = "AccessDenied"; $_200 = "Welcome Admin"; if ($_SERVER["REQUEST_METHOD"] != "POST") die("BugsBunnyCTF is here :p…"); if ( !isset($_POST["flag"]) ) die($_403); foreach ($_GET as $key => $value) $$key = $$value; foreach ($_POST as $key => $value) $$key = $value; if ( $_POST["flag"] !== $flag ) die($_403); else { echo "This is your flag : ". $flag . "\n"; die($_200); } ?>

    从源码文件中可以看出,要想获得flag,你必须知道原来的flag,那是不可能的, 这个时候我们就可以利用变量覆盖漏洞。

    foreach ($_GET as $key => $value) $$key = $$value

    当get上传_200=flag,经过 k e y = key = key=value的处理后,就会变为 _ 200 = \_200= _200=flag,这样就会将原来的 2 00 的 值 覆 盖 掉 , 换 成 _200的值覆盖掉,换成 200flag的值。

    foreach ($_POST as $key => $value) $$key = $value;

    这里的KaTeX parse error: Can't use function '$' in math mode at position 7: key = $̲value,将数组的值当做这个key的值。例如post方法传入flag=abc,则处理后变成$flag=abc。这样就造成将我们需要获取的flag的值给覆盖掉了。不过,当通过get传入_200=flag之后,flag的值将会赋给_200,这时候随便post什么都可以,反正flag已经在_200=flag里面了。

    所以payload:

    get:_200=flag

    post:flag=aaaaa

    复现成功。

    extract()函数

    定义和用法

    extract() 函数从数组中将变量导入到当前的符号表。该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量。该函数返回成功设置的变量数目。具体信息

    语法

    extract(array,extract_rules,prefix)

    参数描述array必需。规定要使用的数组。extract_rules可选。extract() 函数将检查每个键名是否为合法的变量名,同时也检查和符号表中已存在的变量名是否冲突。对不合法和冲突的键名的处理将根据此参数决定。可能的值: EXTR_OVERWRITE - 默认。如果有冲突,则覆盖已有的变量。 EXTR_SKIP - 如果有冲突,不覆盖已有的变量。 EXTR_PREFIX_SAME - 如果有冲突,在变量名前加上前缀 prefix。 EXTR_PREFIX_ALL - 给所有变量名加上前缀 prefix。 EXTR_PREFIX_INVALID - 仅在不合法或数字变量名前加上前缀 prefix。 EXTR_IF_EXISTS - 仅在当前符号表中已有同名变量时,覆盖它们的值。其它的都不处理。 EXTR_PREFIX_IF_EXISTS - 仅在当前符号表中已有同名变量时,建立附加了前缀的变量名,其它的都不处理。 EXTR_REFS - 将变量作为引用提取。导入的变量仍然引用了数组参数的值。prefix可选。如果 extract_rules 参数的值是 EXTR_PREFIX_SAME、EXTR_PREFIX_ALL、 EXTR_PREFIX_INVALID 或 EXTR_PREFIX_IF_EXISTS,则 prefix 是必需的。 该参数规定了前缀。前缀和数组键名之间会自动加上一个下划线。

    例如:

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

    输出结果为:

    $a = Cat; $b = Dog; $c = Horse

    这样就可以实现变量覆盖。

    漏洞复现
    <?php include('flag.php') $test='*************'; extract($_GET); if(isset($gift)){ $content=trim($test); if($gift==$content){ echo 'flag is',$flag; } echo 'error'; }

    变量content的值是通过读取变量test的值获取到的。如果两个变量相等输出flag。如果不相等,输出错误。但是我们并不知道test的值是什么?所以我们使用变量覆盖漏洞,重新给test赋值。

    例如: G E T [ ‘ t e s t ’ ] = ’ a ’ , 被 e x t r a c t ( ) 函 数 处 理 后 , 就 变 成 了 _GET[‘test’]=’a’,被extract()函数处理后,就变成了 GET[test]=a,extract()test=’a’,有与之同名的变量KaTeX parse error: Expected 'EOF', got '\*' at position 9: test = '\̲*̲\*\*\*\*\*\*';,…gift=$content。就可以获得flag。

    构造我们的payload:

    Get方法传值:?gift=a&test=a.

    今天就写这两个把,肚子疼的厉害。


    Parse_str()

    分析

    Parse_str()–将字符串解析成多个变量

    注释:

    parse_str(string,array)

    如果未设置 array 参数,由该函数设置的变量将覆盖已存在的同名变量。

    php.ini 文件中的 magic_quotes_gpc 设置影响该函数的输出。如果已启用,那么在 parse_str() 解析之前,变量会被 addslashes() 转换。

    例子:

    <?php parse_str("name=Bill&age=60"); echo $name."<br>"; echo $age; ?>

    输入:

    Bill

    60

    漏洞复现
    <?php error_reporting(0); //函数规定不同级别错误,这里为关闭错误报告 if(empty($_GET['id'])){ show_source(__FILE__); //显示文件 die(); }else{ include('flag.php'); $a="www.OPENCTF.com"; $id=$_GET['id']; @parse_str($id);//把查询字符串解析到变量中,没有使用array选项,若有同名变量,将原来的覆盖。这里明显将原来的$a的值给覆盖掉。 if($a[0]!='QNKCDZO'&&md5($a[0])==md5('QNKCDZO')){ //判断$a[0]的值不是QNKCDZO并且$a[0]的MD5值要和QNKCDZO的MD5值相同,很难找出这样的字符串。 echo $flag; }else{ exit('其实很简单,其实并不难'); } }

    这里最重要的就是处理if($a[0]!='QNKCDZO'&&md5($a[0])==md5('QNKCDZO'))这个问题,我们要找到一个字符串值和QNKCDZO不同,但是字符串的MD5值和QNKCDZO的MD5值相同。

    这里要利用PHP处理0e开头的MD5哈希字符串的漏洞,PHP在处理哈希字符串时,会利用”!=”或”==”来对哈希值进行比较,它把每一个以”0E”开头的哈希值都解释为0,这样的话两个不同字符串也就可以相等了,都等于0就行。以0e开头的这样的

    而QNKCDZO的MD5加密后的值是0e830400451993494058024219903391,所以再找一个字符串,值和QNKCDZO不要,但MD5加密后的值是0e开头的字符串就行

    构造payload:

    ?id=a[]=s1836677006a`

    参考文章

    最新回复(0)