PHP $_SERVER['HTTP_HOST'] 与 $_SERVER['SERVER_NAME'],我是否正确理解手册页?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1459739/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-25 02:41:07  来源:igfitidea点击:

PHP $_SERVER['HTTP_HOST'] vs. $_SERVER['SERVER_NAME'], am I understanding the man pages correctly?

phpapachesecurityowasp

提问by Jeff

I did a lot of searching and also read the PHP $_SERVER docs. Do I have this right regarding which to use for my PHP scripts for simple link definitions used throughout my site?

我做了很多搜索,还阅读了 PHP $_SERVER 文档。对于我的 PHP 脚本中用于整个站点的简单链接定义,我是否有权使用它?

$_SERVER['SERVER_NAME']is based on your web server's config file (Apache2 in my case), and varies depending on a few directives: (1) VirtualHost, (2) ServerName, (3) UseCanonicalName, etc.

$_SERVER['SERVER_NAME']基于您的 Web 服务器的配置文件(在我的情况下为 Apache2),并且取决于一些指令:(1)VirtualHost,(2)ServerName,(3)UseCanonicalName 等。

$_SERVER['HTTP_HOST']is based on the request from the client.

$_SERVER['HTTP_HOST']是基于客户的请求。

Therefore, it would seem to me that the proper one to use in order to make my scripts as compatible as possible would be $_SERVER['HTTP_HOST']. Is this assumption correct?

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

Followup comments:

后续评论:

I guess I got a little paranoid after reading this article and noting that some folks said "they wouldn't trust any of the $_SERVERvars":

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

Apparently the discussion is mainly about $_SERVER['PHP_SELF']and why you shouldn't use it in the form action attribute without proper escaping to prevent XSS attacks.

显然,讨论主要是关于$_SERVER['PHP_SELF']以及为什么在没有适当转义以防止 XSS 攻击的情况下不应在表单操作属性中使用它。

My conclusion about my original question above is that it is "safe" to use $_SERVER['HTTP_HOST']for all links on a site without having to worry about XSS attacks, even when used in forms.

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

Please correct me if I'm wrong.

如果我错了,请纠正我。

采纳答案by Gumbo

That's probably everyone's first thought. But it's a little bit more difficult. See Chris Shiflett's article SERVER_NAMEVersus HTTP_HOST.

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

It seems that there is no silver bullet. Only when you force Apache to use the canonical nameyou will always get the right server name with SERVER_NAME.

似乎没有银弹。只有当您强制 Apache 使用规范名称时,您才能始终获得带有SERVER_NAME.

So you either go with that or you check the host name against a white list:

因此,您要么使用它,要么根据白名单检查主机名:

$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;
}

回答by Simon East

Just an additional note - if the server runs on a port other than 80 (as might be common on a development/intranet machine) then HTTP_HOSTcontains the port, while SERVER_NAMEdoes not.

只是一个附加说明 - 如果服务器在 80 以外的端口上运行(在开发/内联网机器上可能很常见),则HTTP_HOST包含该端口,而SERVER_NAME没有。

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

(At least that's what I've noticed in Apache port-based virtualhosts)

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

As Mike has noted below, HTTP_HOSTdoes notcontain :443when running on HTTPS (unless you're running on a non-standard port, which I haven't tested).

正如迈克下面提到的,HTTP_HOST没有包含:443在HTTPS运行时(除非你是一个非标准端口,我没有测试运行)。

回答by bobince

Use either. They are both equally (in)secure, as in many cases SERVER_NAME is just populated from HTTP_HOST anyway. I normally go for HTTP_HOST, so that the user stays on the exact host name they started on. For example if I have the same site on a .com and .org domain, I don't want to send someone from .org to .com, particularly if they might have login tokens on .org that they'd lose if sent to the other domain.

使用。它们都同样(不)安全,因为在许多情况下 SERVER_NAME 只是从 HTTP_HOST 填充。我通常使用 HTTP_HOST,这样用户就可以使用他们开始时使用的确切主机名。例如,如果我在 .com 和 .org 域上有相同的站点,我不想将某人从 .org 发送到 .com,特别是如果他们可能在 .org 上有登录令牌,如果发送到他们会丢失另一个域。

Either way, you just need to be sure that your webapp will only ever respond for known-good domains. This can be done either (a) with an application-side check like Gumbo's, or (b) by using a virtual host on the domain name(s) you want that does not respondto requests that give an unknown Host header.

无论哪种方式,您只需要确保您的 web 应用程序只会响应已知良好的域。这可以通过 (a) 使用 Gumbo 之类的应用程序端检查来完成,或者 (b) 通过在您想要的域名上使用虚拟主机,该虚拟主机不会响应提供未知主机标头的请求。

The reason for this is that if you allow your site to be accessed under any old name, you lay yourself open to DNS rebinding attacks (where another site's hostname points to your IP, a user accesses your site with the attacker's hostname, then the hostname is moved to the attacker's IP, taking your cookies/auth with it) and search engine hiHymaning (where an attacker points their own hostname at your site and tries to make search engines see it as the ‘best' primary hostname).

这样做的原因是,如果您允许以任何旧名称访问您的站点,那么您将面临 DNS 重新绑定攻击(其中另一个站点的主机名指向您的 IP,用户使用攻击者的主机名访问您的站点,然后使用主机名访问您的站点)移动到攻击者的 IP,带走您的 cookie/身份验证)和搜索引擎劫持(攻击者将他们自己的主机名指向您的站点,并试图让搜索引擎将其视为“最佳”主要主机名)。

Apparently the discussion is mainly about $_SERVER['PHP_SELF'] and why you shouldn't use it in the form action attribute without proper escaping to prevent XSS attacks.

显然,讨论主要是关于 $_SERVER['PHP_SELF'] 以及为什么你不应该在没有适当转义以防止 XSS 攻击的情况下在表单操作属性中使用它。

Pfft. Well you shouldn't use anythingin anyattribute without escaping with htmlspecialchars($string, ENT_QUOTES), so there's nothing special about server variables there.

噗。好吧,你不应该在任何属性中使用任何东西而不用 转义,所以那里的服务器变量没有什么特别之处。htmlspecialchars($string, ENT_QUOTES)

回答by antitoxic

This is a verbose translation of what Symfony uses to get the host name (see the second example for a more literal translation):

这是 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);
}


Outdated:

过时:

This is my translation to bare PHP of a method used in Symfony framework that tries to get the hostname from every way possible in order of best practice:

这是我对 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);
}

回答by Pacerier

Is it "safe" to use $_SERVER['HTTP_HOST']for all links on a site without having to worry about XSS attacks, even when used in forms?

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

Yes, it's safeto use $_SERVER['HTTP_HOST'], (and even $_GETand $_POST) as long as you verify thembefore accepting them. This is what I do for secure production servers:

是的,只要您在接受它们之前验证它们,就可以安全地使用$_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!';
// ...

The advantage of $_SERVER['HTTP_HOST']is that its behavior is more well-defined than $_SERVER['SERVER_NAME']. Contrast ➫➫:

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

Contents of the Host: header from the current request, if there is one.

Host 的内容:来自当前请求的标头,如果有的话。

with:

和:

The name of the server host under which the current script is executing.

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

Using a better defined interface like $_SERVER['HTTP_HOST']means that more SAPIs will implement it using reliablewell-defined behavior. (Unlike the other.) However, it is still totally SAPI dependent ➫➫:

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

There is no guarantee that every web server will provide any of these [$_SERVERentries]; servers may omit some, or provide others not listed here.

不能保证每个 Web 服务器都会提供这些 [$_SERVER条目] 中的任何一个;服务器可能会省略一些,或提供此处未列出的其他内容。

To understand how to properly retrieve the host name, first and foremost you need to understand that a server which contains only codehas no means of knowing (pre-requisite for verifying) its own nameon the network. It needs to interface with a component that supplies it its own name. This can be done via:

要了解如何正确检索主机名,首先您需要了解仅包含代码的服务器无法知道(验证的先决条件)在网络上的名称。它需要与为其提供自己名称的组件交互。这可以通过:

  • local config file

  • local database

  • hardcoded source code

  • external request (curl)

  • client/attacker's Host:request

  • etc

  • 本地配置文件

  • 本地数据库

  • 硬编码的源代码

  • 外部请求(curl

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

  • 等等

Usually its done via the local (SAPI) config file. Note that you have configured it correctly, e.g. in Apache ➫➫:

通常它是通过本地(SAPI)配置文件完成的。请注意,您已正确配置它,例如在 Apache ➫➫ 中

A couple of things need to be 'faked' to make the dynamic virtual host look like a normal one.

The most important is the server name which is used by Apache to generate self-referential URLs, etc. It is configured with the ServerNamedirective, and it is available to CGIs via the SERVER_NAMEenvironment variable.

The actual value used at run time is controlled bythe UseCanonicalName setting.

WithUseCanonicalName Offthe server name comes from the contents of the Host:header in the request. WithUseCanonicalName DNSit comes from a reverse DNS lookup of the virtual host's IP address. The former setting is used for name-based dynamic virtual hosting, and the latter is used for** IP-based hosting.

IfApache cannot work out the server name because there is no Host:header or the DNS lookup fails thenthe value configured with ServerNameis used instead.

需要“伪造”一些东西才能使动态虚拟主机看起来像一个正常的虚拟主机。

最重要的是 Apache 用来生成自引用 URL 等的服务器名称。它是用ServerName指令配置的,并且可以通过SERVER_NAME环境变量提供给 CGI 。

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

随着UseCanonicalName Off服务器的名字来自内容Host:的请求头。有了UseCanonicalName DNS它来自虚拟主机的IP地址的反向DNS查找。前者设置用于基于名称的动态虚拟托管,后者用于**基于IP的托管。

如果因为没有Apache可以不工作了服务器名称Host:页眉或查找失败的DNS ,然后用配置的值ServerName来代替。

回答by Powerlord

The major difference between the two is that $_SERVER['SERVER_NAME']is a server controlled variable, while $_SERVER['HTTP_HOST']is a user-controlled value.

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

The rule of thumb is to never trust values from the user, so $_SERVER['SERVER_NAME']is the better choice.

经验法则是永远不要相信用户的价值观,因此$_SERVER['SERVER_NAME']是更好的选择。

As Gumbo pointed out, Apache will construct SERVER_NAME from user-supplied values if you don't set UseCanonicalName On.

正如 Gumbo 指出的那样,如果您没有设置UseCanonicalName On.

Edit: Having said all that, if the site is using a name-based virtual host, the HTTP Host header is the only way to reach sites that aren't the default site.

编辑:说了这么多,如果站点使用基于名称的虚拟主机,则 HTTP 主机标头是访问非默认站点的唯一方法。

回答by CallMeLaNN

I am not sure and not really trust $_SERVER['HTTP_HOST']because it depend on header from client. In another way, if a domain requested by client is not mine one, they will not getting into my site because DNS and TCP/IP protocol point it to the correct destination. However I don't know if possible to hiHyman the DNS, network or even Apache server. To be safe, I define host name in environment and compare it with $_SERVER['HTTP_HOST'].

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

Add SetEnv MyHost domain.comin .htaccess file on root and add ths code in Common.php

SetEnv MyHost domain.com在 root 上添加.htaccess 文件并在 Common.php 中添加这些代码

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

I include this Common.php file in every php page. This page doing anything required for each request like session_start(), modify session cookie and reject if post method come from different domain.

我在每个 php 页面中都包含这个 Common.php 文件。此页面为每个请求执行所需的任何操作,例如session_start()修改会话 cookie 并拒绝 post 方法来自不同的域。

回答by Mike

First I want to thank you for all the good answers and explanations. This is the method I created based upon all your answer to get the base url. I only use it in very rare situations. So there is NOT a big focus on security issues, like XSS attacks. Maybe someone needs it.

首先,我要感谢您所有好的答案和解释。这是我根据您的所有答案创建的获取基本 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]; }
}

回答by Jaydeep Dave

XSSwill always be there even if you use $_SERVER['HTTP_HOST'], $_SERVER['SERVER_NAME']OR $_SERVER['PHP_SELF']

XSS即使您使用$_SERVER['HTTP_HOST'],$_SERVER['SERVER_NAME']$_SERVER['PHP_SELF']