PHP 超全局变量的安全性
几年前,我和妻子决定去北方滑雪旅行。要预订滑雪设备,您必须提前 24 小时通过滑雪小屋的在线网站发出通知。问题是我的妻子要求我在截止时间前 23 小时预订。
因此我开始思考并检查了在线网站,该网站不允许您在 24 小时内进行任何预订。但是,一旦您选择了合适的日期,我注意到 URL 是
https://www.somewhere.com/reservations.php?date=01-23-01
我突然想到,尽管他们已经锁定了我可以选择的日期范围的安全性,但最终值还是放在了网址末尾的 GET 语句中。我修改了网址,使用了 “date=01-22-01”,果然,第二天早上第一件事我们的滑雪板就在那里等着我们了(当然,我们付了钱)。
这个看似无辜但很实用的例子只是我们在使用任何可以用在我们意想不到的方式的编程语言时必须注意的危险之一,这引出了我们对 PHP 超全局变量的讨论。
要理解超全局变量,至关重要的是您要理解数据是如何从一个网页传递到另一个网页的(例如,表单)。具体来说,您必须了解两种称为 GET 和 POST 的方法。您还应该熟悉 HTML <FORM> 语句(一个好的参考是 www.w3.org/TR/html401/interact/forms.html)。
您可能以前见过类似这样的代码
<form name="form1" method="post" action="process.php"> <p>Please enter your name:</p> <p><input type="text" name="yourname" /></p> <p><input type="button" name="Submit" value="Submit" /></p> </form>
这是标准的、不花哨的 HTML 表单代码,它请求一些信息,然后将数据提交到文件 process.php。这里的关键部分是 method 声明,它告诉表单 如何 提交数据;为此,我们需要跑题片刻(屏住呼吸)
对于那些记得 HTML 早期的用户来说,表单是通过 <ISINDEX> HTML 标签提供的。通过将此标签插入到 HTML 文档的 HEAD 中,会出现一个文本字段,您可以在其中提供输入。随着新的 HTML+ 标准的发展,设计了一个 <FORM> 标签,可以与 GET、POST 或 PUT 的 METHOD 属性一起使用。因此,这为我们提供了几种不同的发送数据的方式。
使用 GET,变量及其值在 URL 请求的标头中发送,并附加为 URL 本身的一部分。限制是网址 (URL) 限制为 8,192 个字符;如果数据量太长,则会被截断。此外,即使使用 SSL 连接,数据也不会加密,因为它也是网址的一部分。
例如,一个网页可能有这样的表单语句
<form name="form1" method="get" action="process.php"> <p>Please enter your name, e-mail address, and a comment:</p> <p><input type="text" name="yourname" /></p> <p><input type="text" name="email" /></p> <p><input type="text" name="comment" /></p> <p><input type="button" name="Submit" value="Submit" /></p> </form>
当您单击 提交 时,您的 Web 浏览器将获取您在表单中填写的值,并将您重定向到此网址
http://www.fluffygerbil.com/process.php?yourname=fred+smith&email=fred@nowhere.com&comment=I+have+no+comment
注意到表单的值是如何成为网址本身的一部分的吗?这就是 GET 的本质。
对于好奇的人来说,为了完成此事务,实际在 原始 HTTP 传输中发送的是
GET /process.php?yourname=fred+smith&email=fred@nowhere.com&comment=I+have+no+comment HTTP/1.0 Accept: image/gif, image/x-xbitmap, image/jpeg, */* Accept-Language: en-us User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; Q312461) Host: www.fluffygerbils.com Connection: keep-alive
使用 POST,变量及其值在 URL 请求的正文中发送,而不是标头。这种数据传输类型的优点是发送的数据大小没有限制;它包含在 HTTP 请求的正文中,而不是标头中。此外,如果您正在使用 SSL 连接,数据也会被加密——真划算。例如,考虑一个具有如下表单语句的网页
<form name="form1" method="post" action="process.php"> <p>Please enter your name, e-mail address, and a comment:</p> <p><input type="text" name="yourname" /></p> <p><input type="text" name="email" /></p> <p><input type="text" name="comment" /></p> <p><input type="button" name="Submit" value="Submit" /></p> </form>
当您单击“提交”时,您的 Web 浏览器将获取您在表单中填写的值,并将您重定向到此网址: http://www.fluffygerbil.com/process.php
注意到表单的值不是网址本身的一部分吗?这就是 POST 的本质。
对于好奇的人来说,为了完成此事务,实际在原始 HTTP 传输中发送的是
POST /process.php HTTP/1.0Accept: image/gif, image/x-xbitmap, image/jpeg, */* Accept-Language: en-us Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; Q312461) Host: www.fluffygerbils.com Content-Length: 94 Pragma: no-cache Connection: keep-alive yourname=fred+smith email=fred@nowhere.com comment=I+have+no+comment
那么,为什么所有这些背景信息都有用呢?当您安装 PHP 4.2.2 或更高版本时,您可能会注意到在编译 PHP 时,它声明
+--------------------------------------------------------------------+ | *** NOTE *** | | The default for register_globals is now OFF! | | | | If your application relies on register_globals being ON, you | | should explicitly set it to on in your php.ini file. | | Note that you are strongly encouraged to read | | https://php.ac.cn/manual/en/security.registerglobals.php | | about the implications of having register_globals set to on, and | | avoid using it if possible. | +--------------------------------------------------------------------+
这意味着 PHP 将对传递给它的数据非常谨慎,并要求您声明数据应该来自哪个 方法。此外,您应该意识到,除了 GET 和 POST 之外,还有更多方法可以将数据发送到您的 PHP 页面
这使我们想到了超全局变量,这是 PHP 中一个相对较新的概念。例如,上面的图表提出了一个小问题:如果您正在使用变量 $yourname,您如何知道在您的脚本执行期间,它没有被试图入侵您脚本的人通过其他六种变量赋值方法之一重新定义?例如,想象一下有人设法将一个 PHP 脚本上传到您的 Web 服务器,该脚本执行以下操作(Daniel Phoenix 的 php exploit)
<?phpsetcookie("test","../../../../../../etc/passwd");echo "cookie inserted";?>
如果有一种方法可以根据数据最初的赋值方式来隔离变量,那不是很好吗?超全局变量允许您指定应该使用通过特定方法接收的哪些变量。
超全局变量是 PHP 尝试帮助您确定特定值来源的方式。如果您还没有听说过 PHP 4.1.0 中的这个新功能,您将需要开始适应它。大多数 PHP 培训书籍都没有涉及这个主题,因此您需要了解如何过渡到这种新的输入方法。最终,您应该重新访问您的 /usr/local/lib/php.ini 文件并进行以下更改
register_globals = Off
这将防止任何用户提交的变量被注入到您的 PHP 代码中,并可以减少潜在攻击者可能造成的变量污染量。他们将不得不花费额外的时间来伪造提交,并且您的内部变量有效地与用户提交的数据隔离。如果用户随后尝试填写表单,服务器将不会将任何数据分配给全局变量 $name、$email 或 $comment。相反,它会将数据分成以下哈希数组
$_POST['name']$_POST['email']$_POST['comment']
主要的超全局数组有
$_GET['variable'] - 通过 HTTP GET 提供给脚本的变量。类似于已弃用的 HTTP_GET_VARS 数组
$_POST['variable'] - 通过 HTTP POST 提供给脚本的变量。类似于已弃用的 $HTTP_POST_VARS 数组
其他不太常见的超全局数组有
$_COOKIE['variable'] - 通过 HTTP cookies 提供给脚本的变量。类似于已弃用的 $HTTP_COOKIE_VARS 数组
$_REQUEST['variable'] - 通过任何用户输入机制(GET、POST、COOKIE)提供给脚本的变量,因此不可信任。
$_GLOBALS['variable'] - 包含对脚本全局范围内当前可用的每个变量的引用。此数组的键是全局变量的名称。
$_SERVER['variable'] - 由 Web 服务器设置的变量或以其他方式直接与当前脚本的执行环境相关的变量。类似于已弃用的 $HTTP_SERVER_VARS 数组
$_FILES['variable'] - 通过 HTTP post 文件上传提供给脚本的变量。类似于已弃用的 $HTTP_POST_FILES 数组
$_ENV['variable'] - 通过环境提供给脚本的变量。类似于已弃用的 $HTTP_ENV_VARS 数组
$_SESSION['variable'] - 当前已注册到脚本会话的变量。类似于已弃用的 $HTTP_SESSION_VARS 数组
有关更多详细信息,请参阅 www.php.net/manual/en/reserved.variables.php。
因此,与其将 $name 设置为 “John”,不如根据表单数据的提交方式,您将拥有 $_GET['name'] = "John" 或可能是 $_POST['name'] = "John"。 优点是您将知道
$name 永远不会被伪造;如果您的脚本设置了它的值,那就是该值!
$_GET 和 $_POST 数组帮助您确定用户是否将数据作为 URL 的一部分或作为请求正文的一部分附加;因此,您不必担心表单接受 POST 数据,并且值会因有人发送附加了 GET 数据的被黑 URL 而更改。 这很快就会明白,所以请坚持下去...
这些超全局变量允许您不仅对变量的值进行区分,还可以对值最初是如何提供给服务器的进行区分。试图入侵您服务器的人将很难绕过这一点。
最近,使用 PHP 编程可能是一种令人沮丧的体验。安全措施阻止数据被轻易地分配给变量,ISP 通常在没有考虑其受众的情况下实施 PHP,并且 PHP 新手往往会被 GET、POST、超全局变量等术语吓到。然而,一点知识可以大有帮助,希望本文对您的探索有所帮助。
关于 PHP 的安全性,作者:Jordan Dimov
血字的研究:利用 PHP 应用程序中的常见漏洞,作者:Shaun Clowes
本文档是在运行 Slackware Linux 8.1 的 Dell Latitude C400 笔记本电脑上精心制作的。本文档基于 PHP 4.3.0 编写。
David Lechnyr 是俄勒冈大学人力资源部门的网络经理。他拥有社会工作硕士学位,以及 MCSE+I、CNE 和 CCNA 认证。他在过去六年一直从事 Linux 工作,重点是系统安全、网络故障排除和 PHP/MySQL 集成。
电子邮件:david@lechnyr.com