PHP 提供了很多有用的预定义变量,可以在执行脚本的任何位置访问,用于提供大量与环境有关的信息。可以通过这些变量获得关于当前用户会话、用户操作环境和本地操作环境等详细信息。PHP 会创建部分变量,而其他许多变量的可用性和值则取决于操作系统和 Web 服务器。
$_SERVER
$_SERVER 包含由 Web 服务器创建的信息,它提供了服务器和客户配置及其当前请求环境的有关信息。列举部分在应用程序中非常有用的变量如下:
- $_SERVER[‘HTTP_REFERER’] 引导用户达到当前位置的页面的URL,由 user agent 设置决定。并不是所有的用户代理都会设置该项,有的还提供了修改 HTTP_REFERER 的功能。简言之,该值并不可信。
- $_SERVER[‘HTTP_USER_AGENT’] 客户的用户代理,一般会提供操作系统和浏览器的有关信息。
- $_SERVER[‘REMOTE_ADDR’] 客户 IP 地址。
- $_SERVER[‘REQUEST_URI’] 客户请求的URL 的路径部分。
<?php echo "Your browser is: ".$_SERVER['HTTP_USER_AGENT']."<br />"; echo "You are referer from ".$_SERVER['HTTP_REFERER']."<br />"; echo "Your IP is ".$_SERVER['REMOTE_ADDR']."<br />"; echo "Your request uri is ".$_SERVER['REQUEST_URI']."<br />"; ?>
结果显示为:
Your browser is: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5…Firefox/3.6.13
You are referer from http://localhost/php/variable/
Your IP is ::1
Your request uri is /php/variable/06.super_globals.php
查看本环境全部的 $_SERVER 变量可以用循环例举:
foreach ($_SERVER as $var => $value) { echo "$var => $value <br />"; }
当然你也可以使用调试函数 var_dump() 来展示 $_SERVER 这个数组:
var_dump($_SERVER);
详细说明请参考 http://www.php.net/manual/zh/reserved.variables.server.php
这些信息被大量地用于站点流量来源的统计系统中,然而,有些信息未必十分准确,因为某一些信息可以被删除或者伪造的。例如下面我们用 PHP 的 fsockopen() 函数来对远程站点建立一个 socket 连接,并伪造http referer。
伪造 HTTP_REFERER
<?php $referer = "phpstone.com"; $host = "www.example.com"; $http = fsockopen($host, 80, $errno, $errstr, 30); $request = "GET / HTTP/1.1\r\n"; $request .= "Host: ".$host."\r\n"; $request .= "Referer: http://".$referer."\r\n"; $request .= "Connection: Close\r\n\r\n"; fputs ($http, $request); while(!feof($http)){ echo fgets($http, 1024); } fclose($http); ?>
假设 example.com 里面有个纯粹地依靠 $_SERVER[‘HTTP_REFERER’] 来统计进站统计的系统,那么这里就会产生一条记录,访问者通过 http://phpstone.com 到达了 http://www.example.com 这个页面,而实际情况并不是这样。当然,socket 连接的方式对于使用 javascript 的统计页面并不会产生影响,例如 google analytics。
关于使用 head() 函数跳转的 referer 问题
另外一个值得注意的是,在 PHP 的脚本页面如果使用 head(“Location:http://www.example.com”) ,本页面本身是不能作为 referer 的,因为所谓的“引导”页面指的是浏览器的引导,而不是后台脚本的重定向。如果你要使用当前脚本页面作为 referer ,可用的办法是使用 javascript 自动执行表单来达到:
<html> <head></head> <body onload="document.getElementById('go').submit();"> <form action="TARGET LINK" method="get" id="go"></form> </body> </html>
注:这只是一个示例,实际操作中复杂环境可能会产生一些问题,但是通过其他的对应方法应该可以得到解决。
慎用 $_SERVER[‘PHP_SELF’]
很多使用 $_SERVER[‘PHP_SELF’] 来执行表单的动作为代码页本身,请看下面表单操作:
<?php if(!empty($_POST['name'])) { echo "Greetings, {$_POST['name']}, and welcome.<br />"; echo $_SERVER['PHP_SELF']."<br />"; echo $_SERVER['SCRIPT_NAME']."<br />"; echo $_SERVER['REQUEST_URI']; } ?> <form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post"> Enter your name: <input type="text" name="name" /> <input type="submit" /> </form>
表面上看,’PHP_SELF’ 似乎没有什么问题,在我的环境返回的结果如下:
Greetings, test, and welcome.
/php/variable/05._POST.php
/php/variable/05._POST.php
/php/variable/05._POST.php
测试环境的文件路径是,http://localhost/php/variable/05._POST.php ,现在我们在访问路径后面加个”/get”运行的结果如下:
Greetings, test, and welcome.
/php/variable/05._POST.php/get
/php/variable/05._POST.php
/php/variable/05._POST.php/get
查看 HTML 源码,可以看到 “<form action=”/php/variable/05._POST.php/get”…”字段,也就是说,$_SERVER[‘PHP_SELF’] 是可以随着用户输入附加内容而发生改变的,这就可以给攻击者提供便利条件。因此在表单提交里,还是尽量使用 $_SERVER[‘SCRIPT_NAME’] 。
至于 $_SERVER[‘REQUEST_URI’] ,是完全显示用户输入请求的。假设输入访问 http://www.example.com ,example.com 默认展示的页面是 index.php 那么 ‘HTTP_SELF’ 的内容是 “/index.php”,而 ‘REQUEST_URI’ 的内容是 “ / ”,因为用户请求的是 “ / ”。
PS: 在 PHP5 版本中,我发现即使 php.ini 中设置了 register_globals = Off ,在表单提交里使用 <?php $PHP_SELF; ?> 这个老语法代码仍然正常,只有在使用<?php echo $PHP_SELF; ?> 时才会提示未定义的变量,不知道是不是 PHP 的 Bug。