web-dev-qa-db-ja.com

PHP $ _SERVER ['HTTP_Host']対$ _SERVER ['SERVER_NAME']、私はマニュアルページを正しく理解していますか?

私はたくさんの検索をしました、そしてまたPHP $ _ SERVER docs を読みました。私のサイト全体で使用されている単純なリンク定義のためのPHPスクリプトにどちらを使用するかについて、この権利はありますか?

$_SERVER['SERVER_NAME']はあなたのWebサーバーの設定ファイル(私の場合はApache2)に基づいており、いくつかのディレクティブによって異なります:(1)VirtualHost、(2)ServerName、(3)UseCanonicalNameなど。

$_SERVER['HTTP_Host']はクライアントからの要求に基づいています。

したがって、私のスクリプトをできるだけ互換性のあるものにするために使用する適切なものは$_SERVER['HTTP_Host']であるように思われます。この仮定は正しいですか?

フォローアップコメント:

この記事を読んだ後、「$_SERVER変数のどれも信頼していないだろう」と言っている人たちがいることに気づいたことに気づいて、私は少し妄想的になったと思います。

どうやら議論は主に$_SERVER['PHP_SELF']についてであり、なぜXSS攻撃を防ぐために適切なエスケープなしでform action属性でそれを使うべきではないのか。

上記の私の最初の質問についての私の結論は、フォームで使用されていても、XSS攻撃を心配する必要なしにサイト上のすべてのリンクに$_SERVER['HTTP_Host']を使用することが「安全」であるということです。

間違っていたら訂正してください。

146
Jeff

それはおそらく皆の最初の考えです。しかし、もう少し難しいです。 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;
}
135
Gumbo

ちなみに、サーバーが80以外のポート(開発用/イントラネットマシンでは一般的)で実行されている場合、HTTP_Hostにはポートが含まれますが、SERVER_NAMEには含まれません。

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

(少なくとも、Apacheのポートベースの仮想ホストで私が気付いたのはそれです)

Mikeが下記で指摘したように、HTTP_HostはHTTPS上で実行している時にはnot:443を含みます(私がテストしていない非標準ポートで実行しているのでない限り)。

62
Simon East

これはsymfonyがホスト名を取得するために使うものの冗長な翻訳です(もっとリテラルな翻訳については2番目の例を見てください):

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フレームワークで使われているベストプラクティスのために可能な限りの方法でホスト名を取得しようとする方法のbare 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);
}
24
antitoxic

どちらかを使ってください。いずれにせよSERVER_NAMEがHTTP_Hostから移入されているだけなので、どちらも同等に(安全に)安全です。私は通常HTTP_Hostに行きます、それでユーザーは彼らが始めた正確なホスト名のままになります。たとえば、.comドメインと.orgドメインに同じサイトがある場合、.orgから.comに誰かを送信したくありません。他のドメイン.

どちらにしても、あなたのWebアプリケーションが既知のドメインに対してのみ応答することを確認する必要があります。これは、(a)Gumboのようにアプリケーション側でチェックするか、(b)が応答しないように応答しないドメイン名で仮想ホストを使用することで実行できます。未知のHostヘッダを与えるリクエスト。

これは、あなたのサイトに古い名前でアクセスすることを許可すると、DNS再結合攻撃にさらされる可能性があるためです(他のサイトのホスト名があなたのIPを指し、ユーザーは攻撃者のホス​​ト名であなたのサイトにアクセスします) cookie/authとそれを使った攻撃者のIPアドレスへの移動と検索エンジンのハイジャック(攻撃者が自分のサイトで自分のホスト名をポイントし、検索エンジンにそれを「最良の」プライマリホスト名として認識させる)。

どうやら議論は主に$ _SERVER ['PHP_SELF']についてであり、なぜXSS攻撃を防ぐために適切にエスケープせずにform action属性でそれを使うべきではないのか。

Pftt htmlspecialchars($string, ENT_QUOTES)でエスケープせずにany属性で何かを使用しないでください。そのため、サーバー変数について特別なことは何もありません。

23
bobince

フォームで使用されている場合でも、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:ヘッダーのコンテンツ(ある場合)。

で:

現在のスクリプトが実行されているサーバーホストの名前。

$_SERVER['HTTP_Host']などのより適切に定義されたインターフェイスを使用すると、より多くのSAPIがreliable明確に定義された動作を使用して実装します。 ( other とは異なります。)ただし、完全にSAPIに依存しています ➫➫

すべてのWebサーバーがこれらの[$_SERVERエントリ]のいずれかを提供する保証はありません。サーバーは一部を省略したり、ここにリストされていない他のサーバーを提供したりできます。

ホスト名を適切に取得する方法を理解するには、何よりもまず、codeのみを含むサーバーは知る手段がない(検証の前提条件)ことを理解する必要があります- 独自の名前ネットワーク上。独自の名前を提供するコンポーネントとインターフェースする必要があります。これは次の方法で実行できます。

  • ローカル設定ファイル

  • ローカルデータベース

  • ハードコードされたソースコード

  • 外部リクエスト( curl

  • クライアント/攻撃者のHost:リクエスト

通常、ローカル(SAPI)構成ファイルを介して行われます。正しく設定されていることに注意してください。 Apacheで ➫➫

動的仮想ホストを通常のホストのように見せるために、いくつかのことを「偽造」する必要があります。

最も重要なのは、Apacheが自己参照URLなどを生成するために使用するサーバー名です。ServerNameディレクティブで構成され、SERVER_NAME環境変数を介してCGIで使用できます。

実行時に使用される実際の値は制御者 UseCanonicalName設定です。

WithUseCanonicalName Offサーバー名は、リクエストのHost:ヘッダーのコンテンツから取得されます。 WithUseCanonicalName DNSこれは、仮想ホストのIPアドレスの逆DNSルックアップから取得されます。前者の設定は名前ベースの動的仮想ホスティングに使用され、後者はIPベースのホスティング**に使用されます。

If ApacheはHost:ヘッダーがないか、DNSルックアップが失敗するため、サーバー名を解決できませんthenServerNameで設定された値が代わりに使用されます。

9
Pacerier

この2つの主な違いは、$_SERVER['SERVER_NAME']はサーバー制御の変数、$_SERVER['HTTP_Host']はユーザー制御の値です。

経験則では、ユーザーからの値を決して信頼しないことになっているので、$_SERVER['SERVER_NAME']がより良い選択です。

Gumboが指摘したように、UseCanonicalName Onを設定しない場合、Apacheはユーザー指定の値からSERVER_NAMEを構築します。

編集:それでも、サイトが名前ベースの仮想ホストを使用している場合、HTTPホストヘッダーがデフォルトサイトではないサイトに到達する唯一の方法です。

8
Powerlord

$_SERVER['HTTP_Host']はクライアントからのヘッダーに依存しているので、よくわかりませんし、本当に信頼していません。別の方法では、クライアントから要求されたドメインが私のものではない場合、DNSとTCP/IPプロトコルが正しい宛先を指すので、彼らは私のサイトに入りません。しかし、DNS、ネットワーク、さらにはApacheサーバーをハイジャックすることが可能かどうかはわかりません。安全のために、環境内でホスト名を定義し、それを$_SERVER['HTTP_Host']と比較します。

Rootの.htaccessファイルにSetEnv MyHost domain.comを追加し、Common.phpにthsコードを追加します

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

私はこのCommon.phpファイルをすべてのphpページに含めます。このページはsession_start()のようなそれぞれのリクエストに必要なことをしていて、セッションクッキーを修正し、postメソッドが異なるドメインから来た場合は拒否します。

2
CallMeLaNN

$_SERVER['HTTP_Host']$_SERVER['SERVER_NAME'] OR $_SERVER['PHP_SELF']を使用してもXSSは常に存在します。

1
Jaydeep Dave

最初に私はすべての良い答えと説明をありがとうございます。これは、基本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]; }
}
0
Mike