我做了很多搜索,还阅读了PHP $ _SERVER文档.对于在我的网站中使用的简单链接定义,我的PHP脚本使用哪个权限?
$_SERVER['SERVER_NAME']
是基于您的Web服务器的配置文件(在我的情况下是Apache2),并根据一些指令而变化:(1)VirtualHost,(2)ServerName,(3)UseCanonicalName等.
$_SERVER['HTTP_HOST']
基于客户的要求.
因此,在我看来,为了使我的脚本尽可能兼容而使用的正确方法是$_SERVER['HTTP_HOST']
.这个假设是否正确?
后续评论:
我想在读完这篇文章之后我有点偏执,并注意到有些人说"他们不会相信任何一个$_SERVER
变种":
http://markjaquith.wordpress.com/2009/09/21/php-server-vars-not-safe-in-forms-or-links/
http://php.net/manual/en/reserved.variables.server.php#89567(评论:Vladimir Kornea 14-Mar-2009 01:06)
显然,讨论主要是关于$_SERVER['PHP_SELF']
为什么你不应该在表单action属性中使用它而没有适当的转义以防止XSS攻击.
我对上述原始问题的结论是$_SERVER['HTTP_HOST']
,即使在表单中使用,也可以"安全"地使用网站上的所有链接,而不必担心XSS攻击.
如果我错了,请纠正我.
这可能是每个人的第一个想法.但这有点困难.请参阅Chris Shiflett的文章SERVER_NAME
VersusHTTP_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'
(至少这是我在基于Apache端口的虚拟主机中注意到的)
正如迈克下面提到的,HTTP_HOST
并没有包含:443
在HTTPS运行时(除非你是一个非标准端口,我没有测试运行).
使用其中之一 它们同样(安全),因为在许多情况下,SERVER_NAME只是从HTTP_HOST填充.我通常会使用HTTP_HOST,以便用户保持他们开始的确切主机名.例如,如果我在.com和.org域中拥有相同的站点,我不想将.org中的某人发送到.com,特别是如果他们可能在.org上有登录令牌,那么如果他们被发送到另一个域名.
无论哪种方式,您只需要确保您的webapp只会响应已知良好的域名.这可以通过以下方式完成:(a)使用Gumbo的应用程序端检查,或者(b)使用您想要的域名上的虚拟主机,该主机不响应给出未知主机头的请求.
这样做的原因是,如果您允许以任何旧名称访问您的站点,您可以对DNS重新绑定攻击(其他站点的主机名指向您的IP,用户使用攻击者的主机名访问您的站点,然后是主机名)移动到攻击者的IP,带上你的cookie/auth)和搜索引擎劫持(攻击者在你的站点指出他们自己的主机名,并试图让搜索引擎将其视为'最佳'主要主机名).
显然,讨论主要是关于$ _SERVER ['PHP_SELF']以及为什么你不应该在表单action属性中使用它而没有适当的转义以防止XSS攻击.
Pfft.好吧,你不应该在任何属性中使用任何东西而不用转义,所以那里的服务器变量并没有什么特别之处.htmlspecialchars($string, ENT_QUOTES)
这是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框架中使用的方法的PHP的翻译,它尝试按照最佳实践的顺序从各种可能的方式获取主机名:
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['HTTP_HOST']
用于网站上的所有链接而不必担心XSS攻击,即使在表单中使用?
是的,它是安全的使用$_SERVER['HTTP_HOST']
,(甚至$_GET
和$_POST
)只要你确认他们接受他们.这就是我为安全生产服务器所做的事情:
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ $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, 'a.a.com' => null, 'b.com' => null, 'b.b.com' => null )); // ] } if($reject_request){ // log errors // display errors (optional) exit; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ echo 'Hello World!'; // ...
优点$_SERVER['HTTP_HOST']
是它的行为比定义更明确$_SERVER['SERVER_NAME']
.对比➫➫:
主机的内容:来自当前请求的标头(如果有).
有:
当前脚本正在其下执行的服务器主机的名称.
使用更好的定义接口 $_SERVER['HTTP_HOST']
意味着更多的SAPI将使用可靠的明确定义的行为来实现它.(不像其他的.)但是,它仍然是完全依赖于SAPI ➫➫:
无法保证每个Web服务器都能提供任何这些[
$_SERVER
条目]; 服务器可以省略一些,或提供此处未列出的其他服务器.
要了解如何正确检索主机名,首先您需要了解只包含代码的服务器无法知道(验证的先决条件)网络上自己的名称.它需要与为其提供自己名称的组件进行交互.这可以通过以下方式完成:
本地配置文件
本地数据库
硬编码的源代码
外部请求(卷曲)
客户/攻击者的Host:
请求
等等
通常通过本地(SAPI)配置文件完成.请注意,您已正确配置它,例如在Apache➫➫中:
需要"伪造"一些东西才能使动态虚拟主机看起来像普通的虚拟主机.
最重要的是Apache用于生成自引用URL等的服务器名称.它使用该
ServerName
指令进行配置,并通过SERVER_NAME
环境变量提供给CGI .运行时使用的实际值由 UseCanonicalName设置控制.
随着
UseCanonicalName Off
服务器的名字来自内容Host:
的请求头.有了UseCanonicalName DNS
它来自虚拟主机的IP地址的反向DNS查找.前一个设置用于基于名称的动态虚拟主机,后者用于基于IP的主机托管.如果因为没有Apache可以不工作了服务器名
Host:
头或DNS查找失败则与配置的值ServerName
来代替.
两者之间的主要区别在于它$_SERVER['SERVER_NAME']
是服务器控制的变量,$_SERVER['HTTP_HOST']
而是用户控制的值.
经验法则是永远不要相信用户的价值观,因此这$_SERVER['SERVER_NAME']
是更好的选择.
正如Gumbo所指出的,如果你没有设置,Apache将根据用户提供的值构建SERVER_NAME UseCanonicalName On
.
编辑:说了这么多,如果该站点使用的是基于名称的虚拟主机,则HTTP Host标头是访问非默认站点的站点的唯一方法.