提问人:Jeff 提问时间:9/22/2009 最后编辑:MartinJeff 更新时间:5/11/2023 访问量:518716
PHP $_SERVER['HTTP_HOST'] 与 $_SERVER['SERVER_NAME'],我是否正确理解了手册页?
PHP $_SERVER['HTTP_HOST'] vs. $_SERVER['SERVER_NAME'], am I understanding the manual pages correctly?
问:
我做了很多搜索,也阅读了 PHP $_SERVER 文档。我是否有权将哪个脚本用于我的PHP脚本,以便在整个站点中使用的简单链接定义?
$_SERVER['SERVER_NAME']
基于您的 Web 服务器的配置文件(在我的情况下是 Apache2),并且取决于几个指令:(1) VirtualHost、(2) ServerName、(3) UseCanonicalName 等。
$_SERVER['HTTP_HOST']
基于客户端的请求。
因此,在我看来,为了使我的脚本尽可能兼容,正确的使用是 .这个假设正确吗?$_SERVER['HTTP_HOST']
后续评论:
我想在阅读这篇文章并注意到有些人说“他们不会相信任何 vars”后,我有点偏执:$_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)
显然,讨论主要是关于为什么在没有适当转义以防止 XSS 攻击的情况下,您不应该在表单 action 属性中使用它。$_SERVER['PHP_SELF']
我对上面最初问题的结论是,它“安全”地用于网站上的所有链接,而不必担心 XSS 攻击,即使在表单中使用也是如此。$_SERVER['HTTP_HOST']
如果我错了,请纠正我。
答:
这可能是每个人的第一个想法。但这有点困难。请参阅 Chris Shiflett 的文章 SERVER_NAME
Versus HTTP_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;
}
评论
$_SERVER['SERVER_NAME']
$_SERVER['HTTP_HOST']
两者之间的主要区别在于是服务器控制的变量,而是用户控制的值。$_SERVER['SERVER_NAME']
$_SERVER['HTTP_HOST']
经验法则是永远不要相信来自用户的值,因此是更好的选择。$_SERVER['SERVER_NAME']
正如 Gumbo 所指出的,如果您不设置 SERVER_NAME.UseCanonicalName On
编辑:综上所述,如果该站点使用的是基于名称的虚拟主机,则HTTP Host标头是访问非默认站点站点的唯一方法。
评论
Host:
使用其中任何一个。它们都同样(不)安全,因为在许多情况下,SERVER_NAME无论如何都只是从HTTP_HOST填充的。我通常会选择HTTP_HOST,以便用户保留他们开始使用的确切主机名。例如,如果我在 .com 和 .org 域上有相同的站点,我不想将某人从 .org 发送到 .com,特别是如果他们在 .org 上可能有登录令牌,如果发送到其他域,他们会丢失这些令牌。
无论哪种方式,您只需要确保您的 Web 应用程序只会响应已知良好的域。这可以通过 (a) 像 Gumbo 这样的应用程序端检查来完成,或者 (b) 在您想要的域名上使用虚拟主机来完成,该虚拟主机不响应提供未知主机标头的请求。
这样做的原因是,如果您允许以任何旧名称访问您的网站,您将面临 DNS 重新绑定攻击(另一个站点的主机名指向您的 IP,用户使用攻击者的主机名访问您的网站,然后主机名被移动到攻击者的 IP,并带走您的 cookie/身份验证)和搜索引擎劫持(攻击者将自己的主机名指向您的网站并试图使搜索引擎将其视为“最佳”主主机名)。
显然,讨论主要是关于 $_SERVER['PHP_SELF'] 以及为什么在没有适当转义的情况下不应该在表单 action 属性中使用它来防止 XSS 攻击。
噗噗��好吧,你不应该在任何属性中使用任何内容而不转义,所以那里的服务器变量没有什么特别之处。htmlspecialchars($string, ENT_QUOTES)
评论
attacker.com
http://example.com/
http://www.example.com/
http://93.184.216.34/
evil-example.com
我不确定,也不是真的信任,因为它取决于客户端的标头。换句话说,如果客户端请求的域不是我的域,它们将不会进入我的站点,因为 DNS 和 TCP/IP 协议将其指向正确的目的地。但是,我不知道是否有可能劫持DNS,网络甚至Apache服务器。为了安全起见,我在环境中定义了主机名并将其与 进行比较。$_SERVER['HTTP_HOST']
$_SERVER['HTTP_HOST']
在根目录上添加.htaccess文件,并在Common.php中添加代码SetEnv MyHost domain.com
if (getenv('MyHost')!=$_SERVER['HTTP_HOST']) {
header($_SERVER['SERVER_PROTOCOL'].' 400 Bad Request');
exit();
}
我在每个 php 页面中都包含这个 Common.php 文件。此页面执行每个请求所需的任何操作,例如,修改会话cookie,如果post方法来自不同的域,则拒绝。session_start()
评论
Host:
这是 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);
}
评论
if ($host = $_SERVER['HTTP_X_FORWARDED_HOST'])
x = a == 1 ? True : False
另外需要注意的是,如果服务器运行在 80 以外的端口上(这在开发/Intranet 机器上可能很常见),则包含该端口,而不包含该端口。HTTP_HOST
SERVER_NAME
$_SERVER['HTTP_HOST'] == 'localhost:8080'
$_SERVER['SERVER_NAME'] == 'localhost'
(至少这是我在基于Apache端口的虚拟主机中注意到的)
正如 Mike 在下面指出的,在 HTTPS 上运行时不包含(除非您在非标准端口上运行,我尚未测试过)。HTTP_HOST
:443
评论
HTTP_HOST
Host:
XSS
即使您使用 ,也会一直在那里,或者$_SERVER['HTTP_HOST']
$_SERVER['SERVER_NAME']
$_SERVER['PHP_SELF']
用于网站上的所有链接而不必担心 XSS 攻击是否“安全”,即使在表单中使用也是如此?
$_SERVER['HTTP_HOST']
是的,只要您在接受它们之前验证它们,就可以安全使用它们(甚至和)。这就是我为安全生产服务器所做的:$_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']
当前请求的 Host: 标头的内容(如果有)。
跟:
执行当前脚本的服务器主机的名称。
使用定义更好的接口(如)意味着更多的 SAPI 将使用可靠、定义良好的行为来实现它。(与其他不同。但是,它仍然完全依赖于 SAPI ➫➫:$_SERVER['HTTP_HOST']
不能保证每个 Web 服务器都会提供这些 [ 条目] 中的任何一个;服务器可能会省略一些,或者提供此处未列出的其他内容。
$_SERVER
要了解如何正确检索主机名,首先需要了解仅包含代码的服务器无法知道(验证的先决条件)其在网络上的名称。它需要与为其提供自己名称的组件进行交互。这可以通过以下方式完成:
本地配置文件
本地数据库
硬编码源代码
外部请求 (curl)
客户端/攻击者的请求
Host:
等
通常通过本地 (SAPI) 配置文件完成。请注意,您已经正确配置了它,例如在 Apache ➫➫ 中:
需要“伪造”一些东西才能使动态虚拟主机看起来像一个普通的虚拟主机。
最重要的是服务器名称,Apache 使用它来生成自引用 URL 等。它配置了指令,并且可以通过环境变量提供给 CGI。
ServerName
SERVER_NAME
运行时使用的实际值由 UseCanonicalName 设置控制。
服务器名称来自请求中标头的内容。它来自虚拟主机 IP 地址的反向 DNS 查找。前者用于基于名称的动态虚拟主机,后者用于基于 IP 的主机。
UseCanonicalName Off
Host:
UseCanonicalName DNS
如果Apache 无法计算出服务器名称,因为没有标头或 DNS 查找失败,然后改用配置的值。
Host:
ServerName
评论
isset
array_key_exists
array_key_exists
isset
in_array
isset
首先,我要感谢你所有好的回答和解释。 这是我根据您的所有答案创建的获取基本 url 的方法。我只在极少数情况下使用它。因此,没有像XSS攻击那样关注安全问题。也许有人需要它。
// Get base url
function getBaseUrl($array=false) {
$protocol = "";
$host = "";
$port = "";
$dir = "";
// Get protocol
if(array_key_exists("HTTPS", $_SERVER) && $_SERVER["HTTPS"] != "") {
if($_SERVER["HTTPS"] == "on") { $protocol = "https"; }
else { $protocol = "http"; }
} elseif(array_key_exists("REQUEST_SCHEME", $_SERVER) && $_SERVER["REQUEST_SCHEME"] != "") { $protocol = $_SERVER["REQUEST_SCHEME"]; }
// Get host
if(array_key_exists("HTTP_X_FORWARDED_HOST", $_SERVER) && $_SERVER["HTTP_X_FORWARDED_HOST"] != "") { $host = trim(end(explode(',', $_SERVER["HTTP_X_FORWARDED_HOST"]))); }
elseif(array_key_exists("SERVER_NAME", $_SERVER) && $_SERVER["SERVER_NAME"] != "") { $host = $_SERVER["SERVER_NAME"]; }
elseif(array_key_exists("HTTP_HOST", $_SERVER) && $_SERVER["HTTP_HOST"] != "") { $host = $_SERVER["HTTP_HOST"]; }
elseif(array_key_exists("SERVER_ADDR", $_SERVER) && $_SERVER["SERVER_ADDR"] != "") { $host = $_SERVER["SERVER_ADDR"]; }
//elseif(array_key_exists("SSL_TLS_SNI", $_SERVER) && $_SERVER["SSL_TLS_SNI"] != "") { $host = $_SERVER["SSL_TLS_SNI"]; }
// Get port
if(array_key_exists("SERVER_PORT", $_SERVER) && $_SERVER["SERVER_PORT"] != "") { $port = $_SERVER["SERVER_PORT"]; }
elseif(stripos($host, ":") !== false) { $port = substr($host, (stripos($host, ":")+1)); }
// Remove port from host
$host = preg_replace("/:\d+$/", "", $host);
// Get dir
if(array_key_exists("SCRIPT_NAME", $_SERVER) && $_SERVER["SCRIPT_NAME"] != "") { $dir = $_SERVER["SCRIPT_NAME"]; }
elseif(array_key_exists("PHP_SELF", $_SERVER) && $_SERVER["PHP_SELF"] != "") { $dir = $_SERVER["PHP_SELF"]; }
elseif(array_key_exists("REQUEST_URI", $_SERVER) && $_SERVER["REQUEST_URI"] != "") { $dir = $_SERVER["REQUEST_URI"]; }
// Shorten to main dir
if(stripos($dir, "/") !== false) { $dir = substr($dir, 0, (strripos($dir, "/")+1)); }
// Create return value
if(!$array) {
if($port == "80" || $port == "443" || $port == "") { $port = ""; }
else { $port = ":".$port; }
return htmlspecialchars($protocol."://".$host.$port.$dir, ENT_QUOTES);
} else { return ["protocol" => $protocol, "host" => $host, "port" => $port, "dir" => $dir]; }
}
评论