审计策略
全局搜索与变量覆盖相关的函数并对参数进行溯源,判断参数是否可控
变量覆盖
变量覆盖指的是用我们自定义的参数值替换程序原有的变量值,一般变量覆盖漏洞需要结合程序的其它功能来实现完整的攻击。
经常导致变量覆盖的漏洞场景有:
-
$$
-
extract()函数
-
parse_str()函数
-
register_globals开启了全局变量注册
-
import_request_variables()使用不当
$$变量覆盖
$$简介
\\”$$\\”在PHP中用于定义可变变量,可变变量主要用于获取了一个普通变量的值并将该值作为这个可变变量的变量名,例如:
<?php
$a=\\\'aaa\\\';
$aaa=\\\'123\\\';
echo $$a; //$$a=$($a)=$(aaa)=\\\'123\\\'
?>
运行之后的结果如下:
漏洞产生
常见的一些以遍历的方式释放变量的代码可能会导致变量覆盖,例如:
<?php
$id =5;
foreach (array(\\\'_COOKIE\\\',\\\'_POST\\\',\\\'_GET\\\') as $_request)
{
foreach ($$_request as $_key=>$_value)
{
$$_key= $_value;
}
}
if($id == 1) {
echo \\\"flag{Welcome_to_FLy!}\\\";
die();
}
?>
上面的代码会判断用户的请求方式GET、POST、Cookie,之后将请求的参数作为$key,值作为$value,此时如果我们构造“id=1”那么,此时的$key即为id,$value即为1,而此时的$$key就等价于$id,之后在L7的赋值等价于$id=1,从而实现对原有变量$id的覆盖,从下面的运行实例可以看到成功实现了对变量id的覆盖并获取到了flag的值:
extract()变量覆盖
extract()
extract()函数用于将变量从数组中导入到当前符号表,该函数使用数组键名作为变量名,使用数组键值作为变量值,针对数组中的每个元素将在当前符号表中创建对应的一个变量,语法如下:
extract(array,extract_rules,prefix)
漏洞产生
extract的extract_rules默认值为EXTR_OVERWRITE—如果有冲突则覆盖已有的变量,如果使用不当则容易造成变量覆盖漏洞,例如:
<?php
$id=5;
extract($_GET);
if($id==1){
echo \\\"flag{Welcome_to_Fly!}\\\";
}else{
echo \\\"Error!\\\";
}
?>
从以上代码可以看到我们要想获得flag,那么就必须要使得$id==1成立,而在开头已经声明了$id=5,那么怎么办呢?可以看到这里使用了extract函数,而且没有extract_rules即为默认覆盖已有变量,那么我们可以构造\\”?id=1\\”来访问该php文件,实现对参数id的覆盖并获取flag,具体如下:
parse_str()变量覆盖
parse_str()
parse_str()函数用于将查询的字符串解析到变量中,如果未设置array参数则由该函数设置的变量将覆盖已存在的同名变量
语法:
parse_str(string,array)
运行实例:
漏洞产生
<?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,这样的话两个不同字符串也就可以相等了。这里的\\”QNKCDZO\\”经MD5加密后的值是0e830400451993494058024219903391,下面构造以下payload:?id=a[]=s1836677006a
PS:以0e开头的MD5可以参考
https://blog.csdn.net/Fly_hps/article/details/104702138
register_globals变量覆盖
register_globals
register_globals是php.ini里的一个配置,这个配置影响到php如何接收传递过来的参数,当register_globals的值为On的时候,传递过来的值会被直接的注册为全局变量直接使用,而Off的时候我们需要到特定的数组里去得到它
漏洞产生
代码示例:
<?php
echo \\\"Register_globals: \\\".(int)ini_get(\\\"register_globals\\\").\\\"<br/>\\\";
if ($auth){
echo \\\"private!\\\";
}
?>
当register_globals=OFF时,这段代码不会出问题,但是当register_globals=ON时,构造请求?auth=1,变量$auth将自动得到赋值,得到的结果为 Register_globals:1 private!:
PS:从 PHP ? 4.2.0 版开始配置文件中 PHP 指令 register_globals 的默认值从 on 改为 off 了,自 PHP 5.3.0 起废弃,并将自PHP 5.4.0起移除。
import_request_variables()变量覆盖
import_request_variables
import_request_variables() 函数将GET/POST/Cookie变量导入到全局作用域中。如果你禁止了register_globals,但又想用到一些全局变量,那么此函数就很有用
语法格式:
bool import_request_variables ( string $types [, string $prefix ] )
参数说明:
-
$types:指定需要导入的变量,可以用字母 G、P 和 C 分别表示 GET、POST 和 Cookie,这些字母不区分大小写,所以你可以使用 g 、 p 和 c 的任何组合。POST包含了通过 POST 方法上传的文件信息。注意这些字母的顺序,当使用 gp 时,POST 变量将使用相同的名字覆盖 GET 变量。任何 GPC 以外的字母都将被忽略
-
$prefix:变量名的前缀,置于所有被导入到全局作用域的变量之前。所以如果你有个名为 userid 的 GET 变量,同时提供了 pref_ 作为前缀,那么你将获得一个名为 $pref_userid 的全局变量。虽然 prefix 参数是可选的,但如果不指定前缀,或者指定一个空字符串作为前缀,你将获得一个 E_NOTICE 级别的错误
漏洞产生
代码示例1:
<?php
$get_id=0;
import_request_variables(\\\"g\\\", \\\"get_\\\");
echo $get_id;
?>
如上代码所示,如果我们构造一个?id=1,那么将会将导入一个$get_id的全局变量,从而覆盖之前的$_get_id的值,程序运行结果如下:
代码示例2:
<?php
$auth = \\\'0\\\';
import_request_variables(\\\'G\\\');
if($auth == 1){
echo \\\"private!\\\";
}else{
echo \\\"public!\\\";
}
?>
以上代码,如果构造?auth=1,那么将会输出private!,运行结果如下:
PS:该函数在最新版本的PHP中以不支持了,目前可用版本范围为:PHP 4 >= 4.1.0 PHP 5 < 5.4.0
原创文章,作者:七芒星实验室,如若转载,请注明出处:https://www.sudun.com/ask/34266.html