PHP编码安全之三: PHP变量安全

PHP   2023-03-16 10:45   608   0  

本文内容参考自《PHP安全之道》。

主要包括开启register_globals后的全局变量覆盖,可变变量名引起的动态变量覆盖,函数extra()、import_request_variables() 、parse_str()变量覆盖。

全局($GLOBALS)变量覆盖

当php的配置中register_globals全局变量设置开启时, 会自动把url/cookie中传递过去的值注册为全局变量, 这是一个非常危险的设置, 需要关闭。

从PHP4.2开始已经将 register_globals 的默认值从 on 改为 off,从PHP 5.3.0 起废弃并自 PHP 5.4.0 起移除。

错误使用 register_globals = on 的例子:

// 当用户合法的时候,赋值 $authorized = true
if (authenticated_user()) {
    $authorized = true;
}

// 由于并没有事先把 $authorized 初始化为 false,
// 当 register_globals 打开时,可能通过GET auth.php?authorized=1 来定义该变量值
// 所以任何人都可以绕过身份验证
if ($authorized) {
    include "/highly/sensitive/data.php";
}

当 register_globals = on 的时候,上面的代码就会有危险了。如果在上面的代码执行之前加入 $authorized = false 的话,无论 register_globals 是 on 还是 off 都可以,因为用户状态被初始化为未经认证。

更多信息参考手册: 使用 Register Globals

动态变量覆盖

动态变量(或者叫可变变量), 就是一个变量的变量名可以动态的设置和使用,一个可变变量获取了一个普通变量的值作为这个可变变量的变量名。

$Bar = 'a';
$Foo = 'Bar';
$World = 'Foo';
$Hello = 'World';
$a = 'Hello';
echo $a; //输出 Hello
echo $$a; //相当于 echo $Hello, 输出 World
echo $$a; //相当于 echo $World, 输出 Foo
echo $$$a; //相当于 echo $Foo, 输出 Bar
echo $$$$a; //相当于  echo $Bar, 输出 a
echo $$$$$a; //相当于 echo $a, 输出 Hello
echo $$$$$$a; //相当于 echo $Helo, 输出 World
//... 你还可以添加更多的$符号 ...//

下面看一个使用不当造成安全隐患的例子:

foreach($_POST AS $key=>$value){
    $$key = $value; //这里造成了动态变量覆盖
}
if(authenticated_user()){ //认证用户是否登录
    $authorized = true;
}

当用户提交的参数中包含authorized=true时, 在执行"认证用户是否登录"之前$authorized的值已经被设置为true,下面的认证已经没有作用了已经越过了权限验证。

为了避免全局变量覆盖的发生, 应尽量不使用动态变量接收客户端参数。上面的代码可以修改为:

$user_name = $_POST['user_name'];
$password = $_POST['password'];
$authorized = false;//变量初始化
if(authenticated_user($user_name, $password)){ //认证用户是否登录
    $authorized = true;
}

函数 extract() 变量覆盖

extract — 从数组中将变量导入到当前的符号表。

extract( array &$array[, int $flags = EXTR_OVERWRITE[, string $prefix = NULL]] ) : int

该函数检查每个键名看是否可以作为一个合法的变量名,同时也检查和符号表中已有的变量名的冲突。

extract($_REQUEST); //使用extract造成变量覆盖
if(authenticated_user()){ //认证用户是否登录
    $authorized = true;
}

上面的例子发生了与动态变量覆盖同样的bug: 当用户提交的参数中包含authorized=true时, 在执行"认证用户是否登录"之前$authorized的值已经被设置为true,下面的认证已经没有作用了已经越过了权限验证。

为了避免全局变量覆盖的发生, 应尽量不使用extract函数接收客户端参数。上面的代码可以修改为:

$user_name = $_POST['user_name'];
$password = $_POST['password'];
$authorized = false;//变量初始化
if(authenticated_user($user_name, $password)){ //认证用户是否登录
    $authorized = true;
}

函数 import_request_variables() 变量覆盖

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

该函数只适用于PHP 4.1.0 ~ PHP 5.4.0, 其他版本中不存在该函数。

如果在配置中禁用了register_globals但是又希望导入一些全局变量,可能会用到这个函数。

造成bug的过程和修正方法, 基本同前面的一致。

函数 parse_str() 变量覆盖

parse_str — 将字符串解析成多个变量

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

如果 encoded_string 是 URL 传递入的查询字符串(query string),则将它解析为变量并设置到当前作用域(如果提供了 result 则会设置到该数组里 )。

$str = 'first=value&arr[]=foo_bar&arr[]=baz';
//第一种情况: 指定输出变量
parse_str($str, $output);
echo $output['first'].PHP_EOL; // 输出: value
echo $output['arr'][0].PHP_EOL; // 输出: foo_bar
echo $output['arr'][1].PHP_EOL; // 输出: baz
//第二种情况: 不指定输出变量()
parse_str($str);
echo $first.PHP_EOL; // 输出: value
echo $arr[0].PHP_EOL; // 输出: foo_bar
echo $arr[1].PHP_EOL; // 输出: baz

在不指定输出变量的情况下, 很容易出现变量覆盖, 造成系统权限限制失效。比如:

parse_str($GLOBALS['HTTP_RAW_POST_DATA']); //获取post中的变量造成变量覆盖
if(authenticated_user()){ //认证用户是否登录
    $authorized = true;
}

当用户提交的参数中包含authorize=true时, 用户认证失效。上面的代码可以修改为:

parse_str($GLOBALS['HTTP_RAW_POST_DATA'], $output); //获取post中的变量造成变量覆盖
$authorized = false;
if(authenticated_user($user_name, $password)){ //认证用户是否登录
    $authorized = true;
}

极度不建议 在没有 result 参数的情况下使用此函数。

对于第二个参数(输出变量), 在且仅在PHP 7.2中会报警("PHP Deprecated: parse_str(): Calling parse_str() without the result argument is deprecated"), 在其他版本(7.0,7.3,7.4)又不报错。

博客评论
还没有人评论,赶紧抢个沙发~
发表评论
说明:请文明发言,共建和谐网络,您的个人信息不会被公开显示。
闲言碎语
成功就像鬼一样,只有别人遇到过。
赞赏支持

如果觉得博客文章对您有帮助,异或土豪有钱任性,可以通过以下扫码向我捐助。也可以动动手指,帮我分享和传播。您的肯定,是我不懈努力的动力!感谢各位亲~