PHP代码审计-变量覆盖漏洞

举报
亿人安全 发表于 2023/05/29 22:43:02 2023/05/29
【摘要】 变量覆盖漏洞变量覆盖(Dynamic Variable Evaluation)是指变量未被初始化,我们自定义的参数值可以替换程序原有的变量值。漏洞危害:通常结合程序的其它漏洞实现完整的攻击,比如文件上传页面,覆盖掉原来白名单的列表,导致任意文件上传;用户注册页面控制没覆盖的未初始化变量导致SQL。挖掘经验:遍历初始化变量常见的遍历方式释放代码,可能导致变量覆盖漏洞extract()变量覆盖i...

变量覆盖漏洞

变量覆盖(Dynamic Variable Evaluation)是指变量未被初始化,我们自定义的参数值可以替换程序原有的变量值。

漏洞危害:

通常结合程序的其它漏洞实现完整的攻击,比如文件上传页面,覆盖掉原来白名单的列表,导致任意文件上传;用户注册页面控制没覆盖的未初始化变量导致SQL。

挖掘经验:

遍历初始化变量

常见的遍历方式释放代码,可能导致变量覆盖漏洞

extract()变量覆盖

int extract ( $array , extract_rules,prefix )

•$array 关联的数组,受第二个和第三个参数的影响。

•extract_rules 对待非法/数字和冲突的键名的方法将根据取出标记

•prefix 仅在第二个参数特殊时需要,添加前缀

危险函数

•EXTR_OVERWRITE - 默认。如果有冲突,则覆盖已有的变量。

•EXTR_SKIP - 如果有冲突,不覆盖已有的变量。(忽略数组中同名的元素)

•EXTR_PREFIX_SAME - 如果有冲突,在变量名前加上前缀 prefix。自 PHP 4.0.5 起,这也包括了对数字

索引的处理

•EXTR_PREFIX_ALL - 给所有变量名加上前缀 prefix(第三个参数)

•EXTR_PREFIX_INVALID - 仅在非法或数字变量名前加上前缀 prefix。本标记是 PHP 4.0.5 新加的。

•EXTR_IF_EXISTS - 仅在当前符号表中已有同名变量时,覆盖它们的值。其它的都不处理。可以用在已经

定义了一组合法的变量,然后要从一个数组例如 $_REQUEST 中提取值覆盖这些变量的场合。本标记是 PH

P 4.2.0 新加的。

•EXTR_PREFIX_IF_EXISTS - 仅在当前符号表中已有同名变量时,建立附加了前缀的变量名,其它的都不

处理。本标记是 PHP 4.2.0 新加的。

•EXTR_REFS - 将变量作为引用提取。这有力地表明了导入的变量仍然引用了 var_array 参数的值。可以

单独使用这个标志或者在 extract_type 中用 OR 与其它任何标志结合使用。本标记是 PHP 4.3.0 新加

的。

变量覆盖即通过外部输入将某个变量的值给覆盖掉。

通常将可以用自定义的参数值替换原有变量值的情况称为变量覆盖漏洞。

0x02 register_globals全局变量覆盖

php.ini中有一项为register_globals,即注册全局变量,当register_globals=On时,传递过来的值会被直接的注册为全局变量直接使用,而register_globals=Off时,我们需要到特定的数组里去得到它。

注意:register_globals已自 PHP 5.3.0 起废弃并将自 PHP 5.4.0 起移除。

当register_globals=On,变量未被初始化且能够用户所控制时,就会存在变量覆盖漏洞:

1
2
3
4
5
6
7
<?php
echo "Register_globals: " . (int)ini_get("register_globals") . "<br/>";

if ($a) {
  echo "Hacked!";
}
?>

通过GET和POST方式输入变量a的值:

当然,也可以从COOKIE中输入:

0x03 extract()变量覆盖

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
2
3
4
5
6
7
8
9
<?php
$a = "0";
extract($_GET);
if ($a == 1) {
	echo "Hacked!";
} else {
	echo "Hello!";
}
?>

以上示例是以GET为例:

防御方法:在调用extract()时使用EXTR_SKIP保证已有变量不会被覆盖

1
extract($_GET,EXTR_SKIP);

0x04 parse_str()变量覆盖

parse_str()函数通常用于解析URL中的querystring,把查询字符串解析到变量中,如果没有array参数,则由该函数设置的变量将覆盖已存在的同名变量。

函数定义如下:

1
void parse_str ( string $str [, array &$arr ])

当parse_str()函数的参数值可以被用户控制时,则存在变量覆盖漏洞:

1
2
3
4
5
6
7
8
9
10
<?php
$a = 'oop';
parse_str($_SERVER["QUERY_STRING"]);

if ($a == 'mi1k7ea') {
	echo "Hacked!";
} else {
	echo "Hello!";
}
?>

再来看到小题目:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
error_reporting(0);
if(empty($_GET['id'])) {
	show_source(__FILE__);
	die();
} else {
	include ('flag.php');
	$a = "www.mi1k7ea.com";
	$id = $_GET['id'];
	@parse_str($id);
	if ($a[0] != 'QNKCDZO' && md5($a[0]) == md5('QNKCDZO')) {
		echo $flag;
	} else {
		exit('so easy!');
	}
}
?>

是弱类型和变量覆盖的结合:

0x05 mb_parse_str()变量覆盖

mb_parse_str()函数用于解析GET/POST/COOKIE数据并设置全局变量,和parse_str()类似:

1
2
3
4
5
6
7
8
9
10
<?php
$a = 'oop';
mb_parse_str($_SERVER["QUERY_STRING"]);

if ($a == 'mi1k7ea') {
	echo "Hacked!";
} else {
	echo "Hello!";
}
?>

0x06 import_request_variables()变量覆盖

支持版本: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,第二个参数为要注册变量的前缀。

使用这个函数只用简单地指定类型即可,这里G指定导入GET请求中的变量:

1
2
3
4
5
6
7
8
9
10
<?php
$a = "0";
import_request_variables("G");

if ($a == 1) {
  echo "Fucked!";
} else {
  echo "Nothing!";
}
?>


0x07 $$导致的变量覆盖

$$即可变变量,一个可变变量获取了一个普通变量的值作为这个可变变量的变量名。

\$与\$​\$的区别

\$var是一个正常变量,名称为:var,存储任何值,如:string,integer,float等。

\$\$var是一个引用变量,用于存储\$var的值。

看个Demo就清楚了:

1
2
3
4
5
6
7
8
9
10
11
<?php  
	$x = "mi1k7ea";
	$$x = 666;
	echo $x."<br/>";
	echo $$x."<br/>";
	echo $mi1k7ea;
	//也可以写到双引号中解析,输出结果一样
	// echo "$x<br/>";
	// echo "${$x}<br/>";
	// echo "$mi1k7ea";
?>


变量覆盖漏洞

$$导致的变量覆盖问题在CTF代码审计题目中经常在foreach中出现,如以下的示例代码,使用foreach来遍历数组中的值,然后再将获取到的数组键名作为变量,数组中的键值作为变量的值。因此就产生了变量覆盖漏洞。:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
foreach (array('_COOKIE','_POST','_GET') as $_request)  
{
    foreach ($$_request as $_key=>$_value)  
    {
        $$_key=  $_value;
    }
}
$id = isset($id) ? $id : "test";
if($id === "mi1k7ea") {
    echo "flag{xxxxxxxxxx}";
} else {
	echo "Nothing...";
}
?>

这里以GET、POST或COOKIE都能触发,传入id=mi1k7ea后,在foreach语句中,\$_key为id,\$_value为mi1k7ea,进而\$\$_key为$id,从而实现了变量覆盖:

一道CTF题目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
include "flag.php";
$_403 = "Access Denied";
$_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);
}
?>

可以看到,有3个if语句和2个foreach语句。

在if语句中,第一个需要你是通过POST方式进行请求,第二个是需要POST一个flag参数过去,第三个是比较flag参数和包含进来的真正的flag是否相等。

在foreach语句中,第一个是可以将GET的参数进行变量覆盖,第二个是将POST的参数进行变量覆盖,但两个语句的处理是有点区别的即一个键值为\$\$value另一个为\$value。

这里整理一下思路:因为POST的参数必须为flag,则第二个foreach语句的\$key为flag,进而\$\$key为\$flag,从而得到\$flag的值为POST传递的flag参数的值;这里因为第二个foreach语句修改了\$flag原来的值为POST传递的flag参数的值,因而最后一个if语句的条件是恒不成立的,在其后的else代码块逻辑中echo输出出来的只能是修改了的\$flag的值即POST传递的flag参数的值而非原本的\$flag的值、接着输出\$_200变量的值;要想输出原本的\$flag的值,我们需要将原本的\$flag覆盖\$_200变量,因此在第一个foreach语句中通过GET输入_200=flag,从而得到的\$\$key为\$_200、\$\$value为\$flag,从而实现在修改\$flag的值之前将其覆盖到\$_200变量中。

验证一下:

parse_str()变量覆盖

void parse_str ( string $encoded_string [, array &$result ] )

•$encoded_string 输入的字符串

•$result 变量将会以数组元素的形式存入到这个数组,作为替代

import_request_variables()

(PHP 4 >= 4.1.0, PHP 5 < 5.4.0)

import_request_variables — 将 GET/POST/Cookie 变量导入到全局作用域中

bool import_request_variables ( string $types [, string $prefix ] )

•$type 指定需要导入的变量。可以用字母‘G’、‘P’和‘C’分别表示 GET、POST 和 Cookie

•$prefix 变量名前缀

修复方案

• 在php.ini文件中设置register_globals=OFF

• 使用原始变量数组,如$_POST,$_GET等数组变量进行操作

• 不使用foreach语句来遍历$_GET变量,而改用[(index)]来指定

• 验证变量是否存在,注册变量前先判断变量是否存在

反序列化漏洞

序列化与反序列化

• 序列化:把对象转换为字节序列的过程称为对象的序列化

• 反序列化:把字节序列恢复为对象的过程称为对象的反序列化

漏洞成因:

反序列化对象中存在魔术方法,而且魔术方法中的代码可以被控制,

漏洞根据不同的代码可以导致各种攻击,如代码注入、SQL注入、目录遍

历等等

序列化的不同结果

• public

• private

• protect

漏洞本质:

• unserialize函数的变量可控

• php文件中存在可利用的类,类中有魔术方法

魔术方法:

__construct(), __destruct()

__call(), __callStatic()

__get(), __set()

__isset(), __unset()

__sleep(), __wakeup()

__toString()

__invoke()

__set_state()

__clone()

__debugInfo()

漏洞案例

根据不同的代码可以导致各种攻击,如代码注入、SQL注入、目录遍历等

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。