185

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

$_SERVER['SERVER_NAME']基于您的 Web 服务器的配置文件(在我的例子中是 Apache2),并且取决于几个指令:(1)VirtualHost,(2)ServerName,(3)UseCanonicalName 等。

$_SERVER['HTTP_HOST']是根据客户的要求。

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

后续评论:

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

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

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

如果我错了,请纠正我。

4

9 回答 9

166

这大概是每个人的第一个想法。但这有点困难。请参阅Chris Shiflett 的文章SERVER_NAMEVersusHTTP_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;
}
于 2009-09-22T12:25:45.917 回答
91

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

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

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

正如 Mike 在下面指出的,在 HTTPS 上运行时不HTTP_HOST包含(除非您在非标准端口上运行,我还没有测试过)。:443

于 2012-08-21T00:02:45.570 回答
33

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

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

这样做的原因是,如果您允许以任何旧名称访问您的站点,您就会对 DNS 重新绑定攻击持开放态度(另一个站点的主机名指向您的 IP,用户使用攻击者的主机名访问您的站点,然后是主机名移动到攻击者的 IP,并带走您的 cookie/auth)和搜索引擎劫持(攻击者将自己的主机名指向您的站点并试图让搜索引擎将其视为“最佳”主主机名)。

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

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

于 2009-09-22T17:24:34.557 回答
26

这是 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);
}
于 2012-01-18T11:46:03.857 回答
12

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

是的,只要您在接受它们之前验证它们,就可以安全地使用$_SERVER['HTTP_HOST'], (甚至$_GETand $_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条目] 中的任何一个;服务器可能会省略一些,或提供此处未列出的其他内容。

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

  • 本地配置文件

  • 本地数据库

  • 硬编码源代码

  • 外部请求 ( curl )

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

  • ETC

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

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

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

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

UseCanonicalName Off服务器名称来自请求中标头的内容Host: UseCanonicalName DNS来自虚拟主机 IP 地址的反向 DNS 查找。前者用于基于名称的动态虚拟主机,后者用于**基于 IP 的主机。

如果Apache 由于没有Host:标头或 DNS 查找失败而无法计算出服务器名称,使用配置的值ServerName代替。

于 2015-03-05T22:39:22.453 回答
8

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

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

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

编辑:话虽如此,如果站点使用基于名称的虚拟主机,HTTP Host 标头是访问不是默认站点的站点的唯一方法。

于 2009-09-22T13:45:53.397 回答
3

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

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

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

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

于 2010-03-15T01:08:25.653 回答
1

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

于 2014-01-10T09:48:40.347 回答
1

首先,我要感谢您提供的所有好的答案和解释。这是我根据您获取基本网址的所有答案创建的方法。我只在非常罕见的情况下使用它。因此,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]; }
}
于 2019-02-25T21:37:32.513 回答