简体   繁体   English

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

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

I did a lot of searching and also read the PHP $_SERVER docs . 我做了很多搜索,还阅读了PHP $ _SERVER文档 Do I have this right regarding which to use for my PHP scripts for simple link definitions used throughout my site? 对于在我的网站中使用的简单链接定义,我的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'] . 因此,在我看来,为了使我的脚本尽可能兼容而使用的正确的是$_SERVER['HTTP_HOST'] Is this assumption correct? 这个假设是否正确?

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 $_SERVER vars": 我想在读完这篇文章之后我有点偏执,并注意到一些人说“他们不会相信任何$_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']以及为什么你不应该在表单action属性中使用它而没有适当的转义以防止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. 如果我错了,请纠正我。

That's probably everyone's first thought. 这可能是每个人的第一个想法。 But it's a little bit more difficult. 但这有点困难。 See Chris Shiflett's article SERVER_NAME Versus HTTP_HOST . 请参阅Chris Shiflett的文章SERVER_NAMEHTTP_HOST

It seems that there is no silver bullet. 似乎没有银弹。 Only when you force Apache to use the canonical name you 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;
}

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_HOST contains the port, while SERVER_NAME does 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_HOST does not contain :443 when running on HTTPS (unless you're running on a non-standard port, which I haven't tested). 正如迈克在下面提到的那样, HTTP_HOST在HTTPS上运行时包含:443 (除非你在非标准端口上运行,我还没有测试过)。

Use either. 使用其中之一 They are both equally (in)secure, as in many cases SERVER_NAME is just populated from HTTP_HOST anyway. 它们同样(安全),因为在许多情况下,SERVER_NAME只是从HTTP_HOST填充。 I normally go for HTTP_HOST, so that the user stays on the exact host name they started on. 我通常会使用HTTP_HOST,以便用户保持他们开始的确切主机名。 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. 例如,如果我在.com和.org域中拥有相同的站点,我不想将.org中的某人发送到.com,特别是如果他们可能在.org上有登录令牌,那么如果他们被发送到另一个域名。

Either way, you just need to be sure that your webapp will only ever respond for known-good domains. 无论哪种方式,您只需要确保您的webapp只会响应已知良好的域名。 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 respond to requests that give an unknown Host header. 这可以通过以下方式完成:(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 hijacking (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 / auth)和搜索引擎劫持(攻击者在你的站点指出他们自己的主机名,并试图让搜索引擎将其视为'最佳'主要主机名)。

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']以及为什么你不应该在表单action属性中使用它而没有适当的转义以防止XSS攻击。

Pfft. Pfft。 Well you shouldn't use anything in any attribute without escaping with htmlspecialchars($string, ENT_QUOTES) , so there's nothing special about server variables there. 那么你不应该在没有使用htmlspecialchars($string, ENT_QUOTES)转义的任何属性中使用任何东西 ,所以那里的服务器变量并没有什么特别之处。

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

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 safe to use $_SERVER['HTTP_HOST'] , (and even $_GET and $_POST ) as long as you verify them before accepting them. 是的, 只要在接受它们之前验证它们 ,就可以安全地使用$_SERVER['HTTP_HOST'] ,(甚至是$_GET$_POST )。 This is what I do for secure production servers: 这就是我为安全生产服务器所做的事情:

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
$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'] . $_SERVER['HTTP_HOST']的优点是它的行为比$_SERVER['SERVER_NAME']更明确。 Contrast ➫➫ : 对比➫➫

Contents of the Host: header from the current request, if there is one. 主机的内容:来自当前请求的标头(如果有)。

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 reliable well-defined behavior. 使用更好定义的接口,如$_SERVER['HTTP_HOST']意味着更多的SAPI将使用可靠的明确定义的行为来实现它。 (Unlike the other .) However, it is still totally SAPI dependent ➫➫ : (不像其他的 。)但是,它仍然是完全依赖于SAPI ➫➫

There is no guarantee that every web server will provide any of these [ $_SERVER entries]; 无法保证每个Web服务器都会提供这些[ $_SERVER条目]; servers may omit some, or provide others not listed here. 服务器可以省略一些,或提供此处未列出的其他服务器。

To understand how to properly retrieve the host name, first and foremost you need to understand that a server which contains only code has no means of knowing (pre-requisite for verifying) its own name on 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 客户/攻击者的Host:请求

  • etc 等等

Usually its done via the local (SAPI) config file. 通常通过本地(SAPI)配置文件完成。 Note that you have configured it correctly, eg in Apache ➫➫ : 请注意,您已正确配置它,例如在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 ServerName directive, and it is available to CGIs via the SERVER_NAME environment variable. 最重要的是Apache用于生成自引用URL等的服务器名称。它使用ServerName指令进行配置,并通过SERVER_NAME环境变量提供给CGI。

The actual value used at run time is controlled by the UseCanonicalName setting. 运行时使用的实际值 UseCanonicalName设置控制

With UseCanonicalName Off the server name comes from the contents of the Host: header in the request. 使用 UseCanonicalName Off服务器名称来自请求中Host:标头的内容。 With UseCanonicalName DNS it comes from a reverse DNS lookup of the virtual host's IP address. 使用 UseCanonicalName DNS它来自虚拟主机IP地址的反向DNS查找。 The former setting is used for name-based dynamic virtual hosting, and the latter is used for** IP-based hosting. 前一个设置用于基于名称的动态虚拟主机,后者用于基于IP的主机托管。

If Apache cannot work out the server name because there is no Host: header or the DNS lookup fails then the value configured with ServerName is used instead. 如果 Apache无法计算服务器名称,因为没有Host:标头或DNS查找失败, 使用ServerName配置的值。

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 ,Apache将根据用户提供的值构造SERVER_NAME。

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 Host标头是访问非默认网站的唯一方式。

I am not sure and not really trust $_SERVER['HTTP_HOST'] because it depend on header from client. 我不确定并且不太信任$_SERVER['HTTP_HOST']因为它依赖于来自客户端的头。 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. 换句话说,如果客户端请求的域不是我的域,则它们将不会进入我的站点,因为DNS和TCP / IP协议将其指向正确的目标。 However I don't know if possible to hijack the DNS, network or even Apache server. 但是我不知道是否有可能劫持DNS,网络甚至是Apache服务器。 To be safe, I define host name in environment and compare it with $_SERVER['HTTP_HOST'] . 为了安全起见,我在环境中定义主机名并将其与$_SERVER['HTTP_HOST']

Add SetEnv MyHost domain.com in .htaccess file on root and add ths code in Common.php 在root上的.htaccess文件中添加SetEnv MyHost domain.com ,并在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. 我在每个php页面中包含此Common.php文件。 This page doing anything required for each request like session_start() , modify session cookie and reject if post method come from different domain. 此页面执行session_start()等每个请求所需的任何操作,修改会话cookie并拒绝post方法来自不同的域。

即使你使用$_SERVER['HTTP_HOST']$_SERVER['SERVER_NAME'] OR $_SERVER['PHP_SELF'] XSS也会一直存在

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. 因此,没有像XSS攻击那样关注安全问题。 Maybe someone needs it. 也许有人需要它。

// 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]; }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM