簡體   English   中英

PHP $ _SERVER ['HTTP_HOST']與$ _SERVER ['SERVER_NAME'],我是否正確理解了手冊頁?

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

我做了很多搜索,還閱讀了PHP $ _SERVER文檔 對於在我的網站中使用的簡單鏈接定義,我的PHP腳本使用哪個權限?

$_SERVER['SERVER_NAME']基於您的Web服務器的配置文件(在我的情況下為Apache2),並且根據一些指令而變化:(1)VirtualHost,(2)ServerName,(3)UseCanonicalName等。

$_SERVER['HTTP_HOST']基於客戶端的請求。

因此,在我看來,為了使我的腳本盡可能兼容而使用的正確的是$_SERVER['HTTP_HOST'] 這個假設是否正確?

后續評論:

我想在讀完這篇文章之后我有點偏執,並注意到一些人說“他們不會相信任何$_SERVER變量”:

顯然,討論主要是關於$_SERVER['PHP_SELF']以及為什么你不應該在表單action屬性中使用它而沒有適當的轉義以防止XSS攻擊。

關於我上面原始問題的結論是,對網站上的所有鏈接使用$_SERVER['HTTP_HOST']是“安全的”,而不必擔心XSS攻擊,即使在表單中使用也是如此。

如果我錯了,請糾正我。

這可能是每個人的第一個想法。 但這有點困難。 請參閱Chris Shiflett的文章SERVER_NAMEHTTP_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在HTTPS上運行時包含:443 (除非你在非標准端口上運行,我還沒有測試過)。

使用其中之一 它們同樣(安全),因為在許多情況下,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所指出的,如果你沒有設置UseCanonicalName On ,Apache將根據用戶提供的值構造SERVER_NAME。

編輯:說了這么多,如果網站使用的是基於名稱的虛擬主機,HTTP Host標頭是訪問非默認網站的唯一方式。

我不確定並且不太信任$_SERVER['HTTP_HOST']因為它依賴於來自客戶端的頭。 換句話說,如果客戶端請求的域不是我的域,則它們將不會進入我的站點,因為DNS和TCP / IP協議將其指向正確的目標。 但是我不知道是否有可能劫持DNS,網絡甚至是Apache服務器。 為了安全起見,我在環境中定義主機名並將其與$_SERVER['HTTP_HOST']

在root上的.htaccess文件中添加SetEnv MyHost domain.com ,並在Common.php中添加代碼

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

我在每個php頁面中包含此Common.php文件。 此頁面執行session_start()等每個請求所需的任何操作,修改會話cookie並拒絕post方法來自不同的域。

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

首先,我要感謝你們所有的好答案和解釋。 這是我根據您獲得基本網址的所有答案創建的方法。 我只在非常罕見的情況下使用它。 因此,沒有像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]; }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM