在很长一段时间内,PHP作为服务器端脚本语言的最大卖点之一就是会为从表单提交的值自动建立一个全局变量。在PHP 4.1中,PHP的制作者们推荐了一个访问提交数据的替代手段。在PHP 4.2中,他们取消了那种老的做法!正如我将在这篇文章中解释的那样,作出这样的变化的目的是出于安全性的考虑。我们将研究PHP在处理表单提交及其它数据时的新的做法,并说明为什么这样做会提高代码的安全性。
这里有什么错误?
看看下面的这段PHP脚本,它用来在输入的用户名及口令正确时授权访问一个Web页面:
<?php
// 检查用户名及口令
if ($username == 'kevin' and $password == 'secret')
$authorized = true;
?>
<?php if (!$authorized): ?>
<!-- 未授权的用户将在这里给予提示 -->
<p>Please enter your username and password:</p>
<form action="<?=$PHP_SELF?>" method="POST">
<p>Username: <input type="text" name="username" /><br />
Password: <input type="password" name="password" /><br />
<input type="submit" /></p>
</form>
<?php else: ?>
<!-- 有安全要求的HTML内容 -->
<?php endif; ?>
PHP 4.2作了什么改变?
在PHP 4.2中,新安装的PHP中的register_globals选项默认为关闭,因此EGPCS值(EGPCS是Environment、Get、Post、Cookies、Server的缩写 -- 这是PHP中外部变量来源的全部范围)不会被作为全局变量来创建。当然,这个选项还可以通过手工来开启,但是PHP的开发者推荐你将其关闭。要贯彻他们的意图,你需要使用其它的方法来获取这些值。
从PHP 4.1开始,EGPCS值就可以从一组指定的数组中获得:
$_ENV -- 包含系统环境变量
$_GET -- 包含查询字符串中的变量,以及提交方法为GET的表单中的变量
$_POST -- 包含提交方式为POST的表单中的变量
$_COOKIE -- 包含所有cookie变量
$_SERVER -- 包含服务器变量,例如HTTP_USER_AGENT
$_REQUEST -- 包含$_GET、$_POST和$_COOKIE的全部内容
$_SESSION -- 包含所有已注册的session变量
<?php
$username = $_REQUEST['username'];
$password = $_REQUEST['password'];
// 检查用户名和口令
if ($username == 'kevin' and $password == 'secret')
$authorized = true;
?>
<?php if (!$authorized): ?>
<!-- 未授权的用户将在这里给予提示 -->
<p>Please enter your username and password:</p>
<form action="<?=$PHP_SELF?>" method="POST">
<p>Username: <input type="text" name="username" /><br />
Password: <input type="password" name="password" /><br />
<input type="submit" /></p>
</form>
<?php else: ?>
<!-- 有安全要求的HTML内容 -->
<?php endif; ?>
但是这是不是意味着更多的输入?
是的,在象上面这样的简单程序中,使用PHP 4.2常常会增加输入量。但是,还是看看光明的一面吧 -- 你的程序终究是更安全了!
不过认真的说,PHP的设计者并没有完全忽视你的痛苦。在这些新数组中有一个特殊的其它所PHP变量都不具备的特征,它们是完全的全局变量。这对你有什么帮助呢?让我们先对我们的示例进行一下扩充。
为了使得站点中的多个页面可以使用用户名/口令论证,我们将我们用户认证程序写到一个include文件(protectme.php)中:
<?php
/* protectme.php */
function authorize_user($authuser, $authpass)
{
$username = $_POST['username'];
$password = $_POST['password'];
// 检查用户名和口令
if ($username != $authuser or $password != $authpass):
?>
<!-- 未授权的用户将在这里给予提示 -->
<p>Please enter your username and password:</p>
<form action="<?=$PHP_SELF?>" method="POST">
<p>Username: <input type="text" name="username" /><br />
Password: <input type="password" name="password" /><br />
<input type="submit" /></p>
</form>
<?php
exit();
endif;
}
?>
<?php
require('protectme.php');
authorize_user('kevin','secret');
?>
<!-- 有安全要求的HTML内容 -->
function authorize_user($authuser, $authpass)
{
global $HTTP_POST_VARS;
$username = $HTTP_POST_VARS['username'];
$password = $HTTP_POST_VARS['password'];
function authorize_user($authuser, $authpass)
{
$username = $_POST['username'];
$password = $_POST['password'];
这对session有什么影响?
特殊的$_SESSION数组的引入实际上有助于简化session代码。你不需要将session变量申明为全局变量,然后再去留意哪些变量被注册了,你现在可以简单地从$_SESSION['varname']中引用你所有的session变量。
现在让我们来看看另一个用户认证的例子。这一次,我们使用sessions以标志一个在你的网站继续逗留的用户已经经过了用户认证。首先,我们来看看PHP 4.0版本(开启register_globals):
<?php
session_start();
if ($username == 'kevin' and $password == 'secret')
{
$authorized = true;
session_register('authorized');
}
?>
<?php if (!$authorized): ?>
<!-- 显示HTML表单以提示用户登录 -->
<?php else: ?>
<!-- 有安全要求的HTML内容 -->
<?php endif; ?>
<?php
session_start();
if ($username == 'kevin' and $password == 'secret')
$_SESSION['authorized'] = true;
?>
<?php if (!$_SESSION['authorized']): ?>
<!-- 显示HTML表单以提示用户登录 -->
<?php else: ?>
<!-- 有安全要求的HTML内容 -->
<?php endif; ?>
总结
在这篇文章中,我解释了PHP脚本语言作出改变的深层原因。在PHP 4.1中,添加了一组特殊数据以访问外部数据。这些数组可以在任何范围内调用,这使得外部数据的访问更方便。在PHP 4.2中,register_globals被默认关闭以鼓励使用这些数组以避免无经验的开发者编写出不安全的PHP代码。