这一篇主要通过几道CTF题讲解一下PHP变量覆盖漏洞利用,主要涉及到extract()函数,parse_str()函数,import_request_variables()函数,$$运算符。
PHP 5.3.0被废弃,PHP5.4.0被移除
PHP中使用变量并不需要初始化,因此register_globals=On时,变量来源可能是各个不同的地方,比如页面的表单、Cookie等。例如:
1 |
|
当register_globals=Off时,这段代码不会有问题:
但是当register_globals=On时,提交http://localhost/variableCover/index.php/?auth=1
,变量$auth
会自动得到赋值:
如果在代码中已经初始化了$auth变量,也不会有变量覆盖漏洞。
另外通过$GLOBALS获取的变量,也可能导致变量覆盖。
extract()函数从数组中将变量导入到当前符号表。该函数使用数组键名作为变量名,数组键值作为变量值。针对数组中每一个元素,将在当前符号表中创建对应的一个变量。
函数定义如下:
1 | int extract ( array $var_array [, int $extract_type [, string $prefix ]] ) |
其中,第二个参数指定函数将变量导入符号表时的行为,最常见的两个值是EXTR_OVERWRITE和EXTR_SKIP。
当值为EXTR_OVERWRITE时,在将变量导入符号表的过程中,如果变量名发生冲突,则覆盖所有变量;值为EXTR_SKIP则表示跳过不覆盖。若第二个参数未指定,则在默认情况下使用EXTR_OVERWRITE。
当extract()函数从用户可以控制的数组中导出变量且第二个参数未设置或设置为EXTR_OVERWRITE时,就存在变量覆盖漏洞,例如:
1 |
|
在这个例子里,extract()从$_GET中导出变量,从而可以导致任意变量被覆盖,即:
函数定义:
1 | void parse_str (string $str [, array $arr]) |
当parse_str()函数的参数值可以被用户控制时,则存在变量覆盖漏洞:
1 |
|
再来看一道CTF的题目:
1 |
|
也就是PHP弱类型和变量覆盖结合的题目:
mb_parse_str()函数用于解析GET/POST/COOKIE数据并设置全局变量,和parse_str()类似。
支持版本:PHP 4 >= 4.1.0, PHP 5 < 5.4.0
import_request_variables()函数将GET、POST、Cookies中的变量导入到全局。
函数定义如下:
1 | bool import_request_variables (string $types [, string $prefix]) |
$type
代表要注册的变量,G代表GET,P代表POST,C代表COOKIE,第二个参数为要注册变量的前缀,如果没有指定,则将覆盖全局变量。例如:
1 |
|
$$
导致的变量覆盖$$
即可变变量,一个可变变量获取了一个普通变量的值作为这个可变变量的变量名。例如:
1 |
|
其实也就等价于$($x)
,输出为:
1 | ca01h |
$$
导致的变量覆盖问题在CTF代码审计题目中经常在foreach中出现,如以下的示例代码,使用foreach来遍历数组中的值,然后再将获取到的数组键名作为变量,数组中的键值作为变量的值。因此就产生了变量覆盖漏洞。
1 |
|
这里以GET、POST或COOKIE都能触发变量覆盖漏洞,传入id=ca01h后,在foreach语句中,$_key
为id,$_value
为ca01h,进而$$_key
为$id,从而实现了变量覆盖。
最后以一道CTF题为此文章结尾:
1 |
|
可以看到,有3个if语句和2个foreach语句。
在if语句中,第一个需要你是通过POST方式进行请求,第二个是需要POST一个flag参数过去,第三个是比较flag参数和包含进来的真正的flag是否相等。
在foreach语句中,第一个是可以将GET的参数进行变量覆盖,第二个是将POST的参数进行变量覆盖,但两个语句的处理是有点区别的即一个键值为$$value另一个为$value。
这里整理一下思路:
$key
为flag,进而$$key
为$flag
,从而得到$flag
的值为POST传递的flag参数的值。$flag
原来的值为POST传递的flag参数的值,因而最后一个if语句的条件是恒不成立的,在其后的else代码块逻辑中echo输出出来的只能是修改了的$flag
的值即POST传递的flag参数的值而非原本的$flag
的值、接着输出$_200
变量的值。$flag
的值,我们需要将原本的$flag
覆盖$_200
变量,因此在第一个foreach
语句中通过GET输入_200=flag
,从而得到的$$key
为$_200
以及$$value
为$flag
,从而实现在修改$flag
的值之前将其覆盖到$_200
变量中。验证结果: