代码审计中的变量覆盖
0x00 背景
变量覆盖漏洞是用自定义的参数值替换原有变量值,通常会结合程序的其他功能完成攻击。本文以代码审计的形式研究变量覆盖漏洞的原理、挖掘形式、防御方案及缺陷。
0x01 变量覆盖漏洞的产生原理
变量覆盖漏洞的产生主要有两种原因:函数使用不当和$$使用不当。能由于不当使用引发变量覆盖的函数有:extract()、parse_str()、import_request_variables()。
extract()
extract()函数的用法是:extract(array,extract_rules,prefix),具体参数的作用如下:
根据上面的表我们可以发现:有三种情况会出现extract变量覆盖,第一种是第二个参数为EXTR_OVERWRITE,第二种是第二个参数为EXTR_IF_EXISTS,第三种是只传入第一个参数,第二个参数默认值为EXTR_OVERWRITE。例如:
<?php $b=2; $a=array('b'=>'1'); extract($a); echo($b) ?>
执行这段程序后我们发现输出的b的值为1,说明extract函数处理$a之后变量b的值被成功覆盖。
parse_str()
这个函数的作用是把查询字符串注册成变量中,他在注册成为变量之前不会验证变量是否已经存在,所以会导致变量覆盖漏洞。这个函数的具体用法如下:
parse_str(string,array) 参数 功能 string 必需。规定要解析的字符串。 array 可选。规定存储变量的数组的名称。该参数指示变量将被存储到数组中。
测试代码
<?php $b=2; parse_str('b=1'); echo($b); ?>
运行程序发现变量b的值为1,原本的值被覆盖掉。
import_request_variables()
这个函数的作用是将 GET/POST/Cookie 变量导入到全局作用域中注册为变量,用在register_globals被禁止的时候。这个函数的用法如下:
import_request_variables ( string $types [, string $prefix ] ) : bool
$types代表要注册变量,当GPC开启的时候会注册GET、POST、COOKIE参数为变量。prefix
参数作为变量名的前缀,置于所有被导入到全局作用域的变量之前。所以如果你有个名为“userid”的 GET 变量,同时提供了“pref_”作为前缀,那么你将获得一个名为 $pref_userid 的全局变量。一个最简单的例子:
<?php $b=2; import_request_variables('GP'); echo($b); ?>
我们在浏览器提交/?b=1,得到的变量b的输出为1,可见原本的变量被覆盖。
$$变量覆盖
一段很经典的$$变量覆盖:
<?php $a=1; foreach(array('_COOKIE','_POST','_GET')as $_request){ foreach($$_request as $_key => $_value) { echo $_key.'<br />'; $$_key = addslashes($_value); } } echo $a; ?>
在浏览器上提交/?a=2我们得到的输出为
a
2
从代码中我们可以看出$_key为COOKIE、POST、GET的参数,提交a=2的时候,$_key的值为a,还有一个$在a的前面,结合起来$a=addslashes($_value);这样会覆盖已有的变量a的值。
0x02 变量覆盖漏洞的挖掘形式
由于变量覆盖漏洞通常要结合其他业务共同利用,所以在代码审计的时候要尽可能的通读核心文件,函数引起的变量覆盖比较容易挖掘,只需要全局搜索这三个关键的函数,然后回溯变量是否可控即可。extract函数还要考虑第二个参数,import_request_variables()在PHP4-4.1.0和PHP5-5.4.0版本可用。
0x03 防御方案
变量覆盖漏洞常见的漏洞点是做变量注册的时候没有验证变量是否已经存在,所以想要防御变量覆盖漏洞最好使用原始的变量数组,如$_GET、$_POST,或者在注册变量前验证变量是否存在。