PHP $ _SERVER ['HTTP_HOST']対$ _SERVER ['SERVER_NAME']、manページを正しく理解していますか?


167

多くの検索を行い、PHPの$ _SERVERドキュメントも読みました。私のサイト全体で使用されている単純なリンク定義のPHPスクリプトに何を使用するかについて、この権利はありますか?

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

$_SERVER['HTTP_HOST'] クライアントからのリクエストに基づいています。

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

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

この記事を読んだ後、私は少し偏執狂になったと思いますが、一部の人々は「彼らはどの$_SERVER変数も信用しないだろう」と述べました:

どうやら議論は主に$_SERVER['PHP_SELF']、なぜXSS攻撃を防ぐために適切にエスケープせずにフォームアクション属性でそれを使用すべきでないかについてです。

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

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

回答:


149

それはおそらく皆の最初の考えです。しかし、それはもう少し難しいです。Chris Shiflettの記事SERVER_NAMEVersusをHTTP_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;
}

4
笑、その記事を読んだのですが、私の質問には実際には答えられなかったようです。プロ開発者はどちらを使用しますか?どちらか。
ジェフ

2
興味深いことに、Apacheでは、SERVER_NAMEがデフォルトでユーザー指定の値を使用することを知りませんでした。
パワーロード2009

1
@Jeff、ホストに複数のサブ/ドメインは、あなただけの二つの選択肢を持っていることをサーバーの場合$_SERVER['SERVER_NAME']$_SERVER['HTTP_HOST'](脇ユーザの要求に基づいて、いくつかの他のカスタムハンドシェイクを実装するから)。プロの開発者は、完全に理解していないことを信頼していません。そのため、SAPIを完全に正しくセットアップするか(この場合、使用するオプションで正しい結果得られます)、またはSAPIが提供する値が問題にならないようにホワイトリストに登録します。
Pacerier

@ガンボ、特定のSAPIでの深刻な問題のため、「ポート」パッチ適用する必要があります。また、array_key_existsあるよりスケーラブルに比べin_arrayそれはO(N)性能を有します。
Pacerier 2015

2
@Pacerier array_key_existsとin_arrayは異なる動作をします。前者はキーをチェックし、後者は値をチェックするので、単に交換することはできません。また、2つの値の配列がある場合は、O(n)のパフォーマンスについて心配する必要はありません...
eis

74

補足事項-サーバーが80以外のポートで実行されている場合(開発/イントラネットマシンでは一般的である可能性があります)HTTP_HOST、ポートは含まれますが、含まれSERVER_NAMEません。

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

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

マイクは下に述べたように、HTTP_HOSTないではない含まれている:443(あなたは私がテストしていない非標準のポート上で実行している場合を除き)HTTPS上で動作しているとき。


4
注:このポートは、443のHTTP_HOSTにもありません(デフォルトのSSLポート)。
Mike

したがって、言い換えると、の値は、ユーザーが指定しHTTP_HOSTHost:パラメーターとは異なります。それは単にそれに基づいています。
Pacerier 2015

1
@Pacerierいいえ、これは逆です。HTTP_HOSTは、HTTPリクエストで提供されたHost:フィールドです。ポートはその一部であり、デフォルトのポートである場合、ブラウザーはそれを言及しません(HTTPの場合は80、HTTPSの場合は443)
xhienne

29

どちらかを使用します。多くの場合、SERVER_NAMEはとにかくHTTP_HOSTから読み込まれるだけなので、どちらも同等に(安全ではありません)安全です。私は通常HTTP_HOSTを使用するので、ユーザーは開始したときの正確なホスト名を維持します。たとえば、.comドメインと.orgドメインに同じサイトがある場合、特に.orgから.comに誰かを送信したくありません。他のドメイン。

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

この理由は、サイトに古い名前でのアクセスを許可すると、DNS再バインド攻撃にさらされることになります(別のサイトのホスト名がIPを指している場合、ユーザーは攻撃者のホス​​ト名を使用してサイトにアクセスし、ホスト名は攻撃者のIPに移動し、Cookie / Authを使用して)および検索エンジンのハイジャック(攻撃者がサイトで自分のホスト名をポイントし、検索エンジンにそれを「最良の」プライマリホスト名として認識させる)。

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

ふff。さて、あなたは使用しないでください何をして任意のでエスケープせずに属性htmlspecialchars($string, ENT_QUOTES)ので、そこにサーバー変数について何も特別な存在です。


解決策(a)、(b)はそのままでは安全ではありません。HTTPリクエストで絶対URIを使用すると、名前ベースの仮想ホストのセキュリティバイパスが可能になります。したがって、実際のルールは決して SERVER_NAMEまたはHTTP_HOSTを信頼しません
regilero 2014年

@bobince、前述の検索エンジンのハイジャックはどのように機能しますか?検索エンジンは単語をドメインURLにマッピングしますが、IPは扱いません。それでは、なぜ「攻撃者は検索エンジンにattacker.comサーバーのIPの最適な主要ソースとして表示させることができる」という意味ですか?それは検索エンジンにとって何の意味もないようです、それは何をするつもりですか?
Pacerier 2015年

2
Googleは確かに持っていた(そしておそらくまだ何らかの形で持っている)デュープサイトのコンセプト、あなたのサイトはとしてアクセス可能である場合にはそのことをhttp://example.com/http://www.example.com/そしてhttp://93.184.216.34/それは、一つのサイトにそれらを組み合わせたアドレスの中で最も人気のあるを選択し、唯一それにリンクを返しますバージョン。evil-example.com同じアドレスをポイントして、Googleに簡単に見てもらうと、より人気のあるアドレスとしてサイトのジュースを盗むことができます。今日、これがどれほど実用的かはわかりませんが、ロシアのリンクファーム攻撃者が過去にこれを試みたのを見たことがあります。
ボビンス2015年

24

これは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フレームワークで使用されるメソッドの裸の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);
}

1
@StefanNch「このように」と定義してください。
showdev 14

1
@showdev if ($host = $_SERVER['HTTP_X_FORWARDED_HOST'])またはのような条件ステートメントを読み取るのが「難しい」と本当に感じますx = a == 1 ? True : False。私が初めてそれを目にしたのは、私の脳が$ hostのインスタンス化と、「なぜ「=」記号が1つだけなのか」という答えを探していたからです。弱いタイピングのプログラミング言語が嫌いになり始めています。すべてが異なって書かれています。あなたは時間を節約せず、あなたは特別ではありません。私はこの方法でコードを記述しません。時間が経つと、それをデバッグする必要があるのは私だからです。疲れた脳には本当に厄介に見えます!私は英語が英語であることを知っていますが、少なくとも私は試します。
StefanNch 14

1
みんな、私は単にSymfonyからコードを移植しただけです。これが私が取った方法です。すべてにとってそれは重要です-それは機能し、それは非常に完全なようです。私自身、これも十分に読めないことですが、完全に書き直す時間はありませんでした。
抗毒性2014

2
私には元気に見えます。これらは三項演算子であり、適切に使用すると、読みやすさを損なうことなく時間(およびバイト)を節約できます。
showdev 14

1
@antitoxic、-1 Symfonyコーダー(他の多くの人と同様)はこの場合何をしているのか正確には知りません。これはホスト名を与えません(Simonの回答を参照)。これは単にあなたに最高与えるの推測になります間違って何回も。
Pacerier 2015年

11

$_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']です。コントラスト➫➫

Host:ヘッダーのコンテンツ(存在する場合)。

と:

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

より適切に定義されたインターフェースを使用する $_SERVER['HTTP_HOST']ということは、より多くのSAPIが信頼できる明確に定義された動作を使用して実装することを意味します。(とは異なります。)ただし、それでも完全にSAPIに依存しています➫➫

すべてのWebサーバーがこれらの[ $_SERVERエントリ] を提供するという保証はありません。サーバーは一部を省略したり、ここに記載されていないものを提供したりする場合があります。

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

  • ローカル構成ファイル

  • ローカルデータベース

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

  • 外部リクエスト(curl

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

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

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

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

実行時に使用される実際の値は、UseCanonicalName設定によって制御されます。

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

場合は何もありませんので、Apacheは、サーバー名をうまくすることはできませんHost:ヘッダーまたは失敗したルックアップDNS 、その後で構成されている値ServerNameの代わりに使用されますが。


8

2つの主な違い$_SERVER['SERVER_NAME']は、サーバー制御の変数であるのに対し$_SERVER['HTTP_HOST']、ユーザー制御の値です。

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

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

編集:以上すべてのことを言いましたが、サイトが名前ベースの仮想ホストを使用している場合、デフォルトのサイトではないサイトにアクセスするにはHTTPホストヘッダーが唯一の方法です。


わかった。ハングアップは、「ユーザーが$ _SERVER ['HTTP_HOST']の値を変更する方法を教えてください」です。可能ですか?
ジェフ

5
これは、着信要求のHostヘッダーの内容にすぎないため、ユーザーが変更できます。メインサーバー(またはデフォルトの:80にバインドされたVirtualHost )はすべての不明なホストに応答するため、そのサイトのHostタグのコンテンツは任意に設定できます。
パワーロード2009

4
IPベースの仮想ホストは常に特定のIPで応答するため、どのような状況でも HTTPホストの値を信頼できないことに注意してください。
パワーロード2009

1
@ジェフ、「ピザハットの電話番号に電話して、KFCのスタッフに話すことを要求することは可能ですか?」もちろん、あなたはあなたが欲しいものを要求することができます。@Powerlord、これはIPベースの仮想ホストとは関係ありません。サーバーは、IPベースの仮想ホストであるかどうかに関係なく、手動またはSAPIの設定で検証済みHost:ない限り、いかなる状況でもHTTPの値を信頼できません。
Pacerier

3

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

追加SetEnv MyHost domain.comルートに.htaccessファイルにし、common.phpの中THSコードを追加します。

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

このCommon.phpファイルをすべてのphpページに含めます。このページでは、リクエストごとに必要な処理を行ったりsession_start()、セッションCookieを変更したり、postメソッドが異なるドメインからのものである場合は拒否したりします。


1
もちろん、DNSをバイパスすることも可能です。攻撃者はHost:、サーバーのIPに直接fradulent 値を発行できます。
Pacerier 2015


1

まず、良い答えと説明をありがとうございました。これは、ベース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]; }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.