PHP $ _SERVER 与$ _SERVER ,我正确理解手册页?

我做了很多search,并阅读了PHP $ _SERVER文档。 我是否有权利使用我的PHP脚本来处理整个网站中的简单链接定义?

$_SERVER['SERVER_NAME']是基于你的web服务器configuration文件(在我的情况下为Apache2),根据几个指令而有所不同:(1)VirtualHost,(2)ServerName,(3)UseCanonicalName等

$_SERVER['HTTP_HOST']基于来自客户端的请求。

因此,在我看来,为了使我的脚本尽可能兼容,使用正确的是$_SERVER['HTTP_HOST'] 。 这个假设是否正确?

后续评论:

我想我读了这篇文章后有点偏执,注意到有些人说“他们不会相信任何$_SERVER变数”:

显然讨论主要是关于$_SERVER['PHP_SELF']以及为什么你不应该在form action属性中使用它而没有正确的转义来防止XSS攻击。

我对上面的原始问题的结论是,对于站点上的所有链接使用$_SERVER['HTTP_HOST']而不必担心XSS攻击是“安全的”,即使在表单中使用。

如果我错了,请纠正我。

这可能是每个人的第一个想法。 但是这有点困难。 请参阅Chris Shiflett的文章SERVER_NAMEHTTP_HOST

似乎没有银弹。 只有当您强制Apache使用规范名称时,您才会始终使用SERVER_NAME获取正确的服务器名称。

所以,你要么去,要么检查主机名与白名单:

 $allowed_hosts = array('foo.example.com', 'bar.example.com'); if (!isset($_SERVER['HTTP_HOST']) || !in_array($_SERVER['HTTP_HOST'], $allowed_hosts)) { header($_SERVER['SERVER_PROTOCOL'].' 400 Bad Request'); exit; } 

另外需要注意的是,如果服务器运行在80以外的端口上(如开发/内联网机器上常见的那样),那么HTTP_HOST包含端口,而SERVER_NAME则不包含端口。

 $_SERVER['HTTP_HOST'] == 'localhost:8080' $_SERVER['SERVER_NAME'] == 'localhost' 

(至less这是我在Apache基于端口的虚拟主机中注意到的)

正如Mike在下面指出的那样,在HTTPS上运行时, HTTP_HOST不包含:443 (除非你运行在一个非标准的端口上,我没有testing过)。

这是Symfony用于获取主机名称的详细翻译( 更多直接翻译请参见第二个示例 ):

 function getHost() { $possibleHostSources = array('HTTP_X_FORWARDED_HOST', 'HTTP_HOST', 'SERVER_NAME', 'SERVER_ADDR'); $sourceTransformations = array( "HTTP_X_FORWARDED_HOST" => function($value) { $elements = explode(',', $value); return trim(end($elements)); } ); $host = ''; foreach ($possibleHostSources as $source) { if (!empty($host)) break; if (empty($_SERVER[$source])) continue; $host = $_SERVER[$source]; if (array_key_exists($source, $sourceTransformations)) { $host = $sourceTransformations[$source]($host); } } // Remove port number from host $host = preg_replace('/:\d+$/', '', $host); return trim($host); } 

已过期:

这是我翻译成在Symfony框架中使用的一种方法,试图从最佳实践的顺序获得每一个可能的主机名:

 function get_host() { if ($host = $_SERVER['HTTP_X_FORWARDED_HOST']) { $elements = explode(',', $host); $host = trim(end($elements)); } else { if (!$host = $_SERVER['HTTP_HOST']) { if (!$host = $_SERVER['SERVER_NAME']) { $host = !empty($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : ''; } } } // Remove port number from host $host = preg_replace('/:\d+$/', '', $host); return trim($host); } 

使用任一。 它们同样安全,因为在许多情况下SERVER_NAME只是从HTTP_HOST填充。 我通常会使用HTTP_HOST,以便用户保持它们开始的确切主机名。 例如,如果我在.com和.org域中拥有相同的站点,我不想从.org发送某人到.com,特别是如果他们可能在.org上拥有login令牌,那么如果发送给另一个领域。

无论哪种方式,你只需要确保你的web应用程序将只响应已知的良好的域名。 这可以通过(a)像Gumbo那样的应用程序端检查来完成,或者(b)通过在你想要的域名上使用一个虚拟主机来对没有给出未知主机头的请求做出响应

原因是,如果你允许你的站点以任何旧的名字被访问,你就会面对DNS重新绑定攻击(其他站点的主机名指向你的IP,用户访问你的站点的攻击者的主机名,然后主机名被移动到攻击者的IP地址,带着你的cookies / auth)和search引擎劫持(攻击者在你的站点指向他们自己的主机名,并试图使search引擎将其视为“最好的”主要主机名)。

显然这个讨论主要是关于$ _SERVER ['PHP_SELF']以及为什么你不应该在form action属性中使用它而没有正确的转义来防止XSS攻击。

Pfft。 那么你不应该使用htmlspecialchars($string, ENT_QUOTES)转义的任何属性的任何东西 ,所以没有什么特别的服务器variables。

两者之间的主要区别在于$_SERVER['SERVER_NAME']是一个服务器控制的variables,而$_SERVER['HTTP_HOST']是一个用户控制的值。

经验法则是不要信任用户的值,所以$_SERVER['SERVER_NAME']是更好的select。

正如Gumbo指出的那样,如果你没有设置UseCanonicalName On ,Apache将从用户提供的值构造SERVER_NAME。

编辑:说了这么多,如果该网站使用基于名称的虚拟主机,HTTP主机头是唯一的方式到达网站不是默认站点。

对于站点上的所有链接使用$_SERVER['HTTP_HOST']是否“安全”,即使在表单中使用时也不必担心XSS攻击?

是的,使用$_SERVER['HTTP_HOST'] (甚至$_GET$_POST是安全的,只要你在接受它们之前validation它们。 这就是我为安全生产服务器所做的:

 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ $reject_request = true; if(array_key_exists('HTTP_HOST', $_SERVER)){ $host_name = $_SERVER['HTTP_HOST']; // [ need to cater for `host:port` since some "buggy" SAPI(s) have been known to return the port too, see http://goo.gl/bFrbCO $strpos = strpos($host_name, ':'); if($strpos !== false){ $host_name = substr($host_name, $strpos); } // ] // [ for dynamic verification, replace this chunk with db/file/curl queries $reject_request = !array_key_exists($host_name, array( 'a.com' => null, 'aacom' => null, 'b.com' => null, 'bbcom' => null )); // ] } if($reject_request){ // log errors // display errors (optional) exit; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ echo 'Hello World!'; // ... 

$_SERVER['HTTP_HOST']的优点是它的行为比$_SERVER['SERVER_NAME']更明确。 对比度➫➫ :

当前请求的Host:头部的内容(如果有的话)。

有:

在其下执行当前脚本的服务器主机的名称。

使用更好的定义接口,如$_SERVER['HTTP_HOST']意味着更多的SAPI将使用可靠的定义良好的行为来实现它。 (与其他不同)然而,它仍然完全依赖于SAPI➫➫ :

不能保证每个Web服务器都会提供这些[ $_SERVER条目]; 服务器可能会省略一些,或者提供这里没有列出的其他人。

要理解如何正确检索主机名,首先需要了解的是,只包含代码的服务器在networking上无法知道(需要先validation) 自己的名字 。 它需要与提供自己的名字的组件接口。 这可以通过以下方式完成:

  • 本地configuration文件

  • 本地数据库

  • 硬编码的源代码

  • 外部请求( curl )

  • 客户端/攻击者的Host:请求

  • 等等

通常通过本地(SAPI)configuration文件完成。 请注意,您已经正确configuration了它,例如在Apache➫➫中 :

有几件事情需要“伪装”,使dynamic虚拟主机看起来像一个正常的。

最重要的是Apache用于生成自引用URL的服务器名称等。它使用ServerName指令进行configuration,并通过SERVER_NAME环境variables可供CGI使用。

运行时使用的实际值 UseCanonicalName设置控制

使用 UseCanonicalName Off服务器名称来自请求中Host:头的内容。 使用 UseCanonicalName DNS它来自虚拟主机IP地址的反向DNS查询。 前者设置用于基于名称的dynamic虚拟主机,后者用于基于IP的主机。

如果 Apache无法解决服务器名称,因为没有Host:头或DNS查找失败, 使用ServerNameconfiguration的值代替。

我不确定和不信任$_SERVER['HTTP_HOST']因为它依赖于来自客户端的头。 换句话说,如果客户端请求的域名不是我的域名,他们将不会进入我的网站,因为DNS和TCP / IP协议指向正确的目的地。 但是我不知道是否有可能劫持DNS,networking甚至Apache服务器。 为了安全起见,我在环境中定义主机名,并与$_SERVER['HTTP_HOST']

在根目录下的.htaccess文件中添加SetEnv MyHost domain.com ,并在Common.php中添加代码

 if (getenv('MyHost')!=$_SERVER['HTTP_HOST']) { header($_SERVER['SERVER_PROTOCOL'].' 400 Bad Request'); exit(); } 

我将这个Common.php文件包含在每个php页面中。 这个页面为session_start()等每个请求做任何事情,如果post方法来自不同的域,修改会话cookie并拒绝。

即使使用$_SERVER['HTTP_HOST']$_SERVER['SERVER_NAME']$_SERVER['PHP_SELF'] XSS也将始终存在