PHP $ SERVER ['HTTP HOST'] vs. $ SERVER ['server NAME'], правильно ли я понимаю man-страницы?


Я много искал, а также читал PHP $ _SERVER docs. Имею ли я это право в отношении того, что использовать для моих PHP-скриптов для простых определений ссылок, используемых на моем сайте?

$_SERVER['SERVER_NAME'] основан на конфигурационном файле вашего веб-сервера (Apache2 в моем случае) и зависит от нескольких директив: (1) VirtualHost, (2) ServerName, (3) UseCanonicalName и т. д.

$_SERVER['HTTP_HOST'] основано на запросе от клиента.

поэтому мне кажется, что правильным использовать для того, чтобы сделать мои скрипты максимально совместимыми, было бы $_SERVER['HTTP_HOST']. Правильно ли это предположение?

продолжение комментария:

Я думаю, что я немного параноик после прочтения этой статьи и отметив, что некоторые люди сказали: "они не будут доверять ни одному из $_SERVER Варс":

по-видимому, речь идет в основном о $_SERVER['PHP_SELF'] и почему вы не должны использовать его в атрибуте действия формы без надлежащего экранирования, чтобы предотвратить XSS атаки.

мой вывод о моем первоначальном вопросе выше заключается в том, что он" безопасен " для использования $_SERVER['HTTP_HOST'] для всех ссылок на сайте без необходимости беспокоиться о XSS атак, даже при использовании в формах.

пожалуйста, поправьте меня, если я ошибаюсь.

8 140

8 ответов:

Это, наверное, первая мысль каждого. Но это немного сложнее. Смотрите статья Криса Шифлетта SERVER_NAME и 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;
}

просто дополнительное примечание-если сервер работает на порту, отличном от 80 (как это может быть распространено на машине разработки / интрасети), то HTTP_HOST содержит порт, в то время как SERVER_NAME нет.

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

(по крайней мере, это то, что я заметил в Apache port-based virtualhosts)

как я отметил ниже, HTTP_HOST тут не содержат :443 при работе на HTTPS (если вы не работаете на нестандартном порту, который я не тестировал).

это подробный перевод того, что 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);
}

устарел:

Это мой перевод на голый PHP метода, используемого в Symfony framework, который пытается получить имя хоста из всех возможных способов в порядке лучшей практики:

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_NAME просто заполняется из HTTP_HOST в любом случае. Я обычно иду на HTTP_HOST, так что пользователь остается на точное имя хоста, на котором они начали. Например, если у меня есть один и тот же сайт в домене .com и .org, я не хочу отправлять кого-то из .org в .com, особенно если у них могут быть маркеры входа в .org, которые они потеряют при отправке в другой домен.

в любом случае, вы просто должны быть уверены, что ваш веб-приложение будет отвечать только за известные хорошие домены. Это можно сделать либо (а) с помощью проверки на стороне приложения, такой как Gumbo's, либо (б) с помощью виртуального хоста на доменных именах, которые вы хотите, чтобы не отвечает к запросам, которые дают неизвестный заголовок узла.

причина этого заключается в том, что если вы разрешаете доступ к вашему сайту под любым старым именем, вы открываете себя для атак повторной привязки DNS (где имя хоста другого сайта указывает на ваш IP, пользователь обращается к вашему сайту с помощью имя хоста злоумышленника, затем имя хоста перемещается на IP-адрес злоумышленника, принимая ваши куки / auth с ним) и захват поисковой системы (где злоумышленник указывает свое собственное имя хоста на вашем сайте и пытается заставить поисковые системы видеть его как "лучшее" основное имя хоста).

по-видимому, речь идет в основном о $_SERVER['PHP_SELF'] и почему вы не должны использовать его в атрибуте действия формы без надлежащего экранирования для предотвращения атак XSS.

Pfft. Ну вы не должны использовать что-нибудь на любой атрибут без экранирования с htmlspecialchars($string, ENT_QUOTES), Так что там нет ничего особенного о переменных сервера.

основное различие между ними состоит в том, что $_SERVER['SERVER_NAME'] - это управляемая сервером переменная, в то время как $_SERVER['HTTP_HOST'] - это контролируемое пользователем значение.

эмпирическое правило-никогда не доверять значениям от пользователя, поэтому $_SERVER['SERVER_NAME'] - Это лучший выбор.

как указал Гумбо, Apache построит SERVER_NAME из предоставленных Пользователем значений, если вы не установите UseCanonicalName On.

Edit: сказав Все это, если сайт использует виртуальный хост на основе имени, заголовок Хоста HTTP является единственный способ добраться до сайтов, которые не являются сайтом по умолчанию.

это "безопасно" использовать $_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'] означает, что больше SAPIs будет реализовывать его с помощью надежный четко определенное поведение. (В отличие от другое.) Тем не менее, это все еще совершенно САПИ зависимая ➫ ➫:

нет никакой гарантии, что каждый веб-сервер предоставит любую из них [$_SERVER записи]; сервер может опустить некоторые из них или предоставить другие, не перечисленные здесь.

чтобы понять, как правильно получить имя хоста, в первую очередь вам нужно понять, что сервер, который содержит только код не имеет возможности знать (необходимое условие для проверки)имя собственное на сеть. Он должен взаимодействовать с компонентом, который предоставляет ему свое собственное имя. Это можно сделать с помощью:

  • локальный файл config

  • локальная база данных

  • жестко исходный код

  • внешний запрос (curl)

  • клиент/нападающий Host: запрос

  • etc

обычно это делается через локальный (SAPI) конфигурационный файл. Обратите внимание, что вы настроили его правильно, например, в Apache ➫ ➫:

несколько вещей должны быть "подделаны", чтобы динамический виртуальный хост выглядел как обычный.

наиболее важным является имя сервера, которое используется Apache для создания самореферентных URL-адресов и т. д. Он настроен с помощью ServerName директива, и она доступна для CGIs через SERVER_NAME переменные среды.

фактическое значение, используемое во время выполнения контролируется параметр UseCanonicalName.

СUseCanonicalName Off имя сервера происходит из содержимого Host: заголовок в запросе. СUseCanonicalName DNS это происходит от обратного DNS-поиска IP-адреса виртуального хоста. Первый параметр используется для имен на основе динамический виртуальный хостинг, причем последний используется для * * IP-хостинга.

если Apache не может выработать имя сервера, потому что нет Host: заголовок или поиск DNS не удается затем значение, настроенное с помощью ServerName используется вместо этого.

Я не уверен, и не очень доверяю $_SERVER['HTTP_HOST'] потому что это зависит от заголовка от клиента. С другой стороны, если домен, запрошенный клиентом, не является моим, они не попадут на мой сайт, потому что протокол DNS и TCP/IP указывают на правильное назначение. Однако я не знаю, можно ли захватить DNS, сеть или даже сервер Apache. Чтобы быть в безопасности, я определяю имя хоста в среде и сравниваю его с $_SERVER['HTTP_HOST'].

добавить SetEnv MyHost domain.com in .файл htaccess в корневом каталоге и добавьте код ths Общий.php

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

Я включаю это общее.php-файл на каждой странице php. Эта страница делает все необходимое для каждого запроса, как session_start(), изменить cookie сеанса и отклонить, если метод post пришел из другого домена.

XSS всегда будет там, даже если вы используете $_SERVER['HTTP_HOST'],$_SERVER['SERVER_NAME'] или $_SERVER['PHP_SELF']