В чем разница между HTTP_HOST и SERVER_NAME в PHP?

533

В чем разница между HTTP_HOSTи SERVER_NAMEв PHP?

где:

  • HTTP_POST === $_SERVER['HTTP_HOST']
  • SERVER_NAME === $_SERVER['SERVER_NAME']

Когда вы рассмотрите возможность использования одного над другим и почему?

Эмануил Русев
источник
14
«Обычно я выбираю HTTP_HOST, чтобы пользователь оставался на том же имени хоста, с которого он начал. Например, если у меня один и тот же сайт в домене .com и .org, я не хочу отправлять кого-то из .org в .com, особенно если у них могут быть токены входа в систему .org, которые они потеряли бы, если бы отправили на другой домен. " - Это и некоторые другие интересные моменты от stackoverflow.com/questions/1459739/…
Ярин
5
@ Yarin, не забудьте белый список - проверьте результатыHTTP_HOST . В противном случае злоумышленник может ввести любое значение в Host:запрос HTTP и заставить сервер принять его.
Pacerier
6
Новички: этот вопрос относится к значениям, которые обычно получают с помощью $_SERVER['HTTP_HOST']или$_SERVER['SERVER_NAME']
Грегори Космо Haun

Ответы:

780

HTTP_HOSTПолучается из заголовка запроса HTTP , и это то , что клиент на самом деле используется в качестве «целевого хоста» запроса. Это SERVER_NAMEопределяется в конфигурации сервера. Какой из них использовать, зависит от того, для чего он вам нужен. Однако теперь вы должны понимать, что одно является контролируемым клиентом значением, которое, таким образом, может быть ненадежным для использования в бизнес-логике, а другое - управляемым сервером значением, которое является более надежным. Однако вам необходимо убедиться, что данный веб-сервер SERVER_NAMEправильно настроен. Взяв Apache HTTPD в качестве примера, вот выдержка из его документации :

Если нет ServerName, сервер пытается определить имя хоста, выполнив обратный поиск по IP-адресу. Если порт не указан ServerName, то сервер будет использовать порт из входящего запроса. Для оптимальной надежности и предсказуемости вы должны указать явное имя хоста и порт, используя ServerNameдирективу.


Обновление : после проверки ответа Пекки на ваш вопрос, который содержит ссылку на ответ Бобинса, для которого PHP всегда будет возвращать HTTP_HOSTзначение SERVER_NAME, что противоречит моему собственному опыту PHP 4.x + Apache HTTPD 1.2.x, накопленному пару лет назад. Я выплеснул немного пыли из моей текущей среды XAMPP в Windows XP (Apache HTTPD 2.2.1 с PHP 5.2.8), запустил его, создал страницу PHP, которая печатает оба значения, создал тестовое приложение Java, используя URLConnectionдля изменения Hostзаголовка и тесты научили меня, что это действительно (неправильно) дело.

После того, как я впервые заподозрил PHP и покопался в некоторых отчетах об ошибках PHP по этой теме, я узнал, что корень проблемы в используемом веб-сервере, что он неверно возвращал HTTP- Hostзаголовок при SERVER_NAMEзапросе. Поэтому я копался в отчетах об ошибках Apache HTTPD, используя различные ключевые слова по теме, и в конце концов обнаружил связанную ошибку . Такое поведение было введено начиная с Apache HTTPD 1.3. Вам нужно установить UseCanonicalNameдирективы onв <VirtualHost>записи о ServerNameдюйме httpd.conf(также проверить предупреждение в нижней части документа !).

<VirtualHost *>
    ServerName example.com
    UseCanonicalName on
</VirtualHost> 

Это сработало для меня.

Подведем итог, SERVER_NAMEэто более надежно, но вы зависите от конфигурации сервера!

BalusC
источник
5
Хорошо, это решает мою проблему, которая не связана с ОП, но имеет отношение к делу. Я был очень обеспокоен вопросами безопасности, используя все, что мог предоставить браузер. Этот ответ был ОГРОМНОЙ помощью. Спасибо, что нашли время собрать это вместе.
Ицхак
2
Почему вы говорите, что HTTP_HOST не надежен? Да, он предоставляется пользователем, но если пользователь даст какое-то поддельное значение, конфигурация вашего сервера автоматически вернет 503, и ваш PHP-скрипт даже не запустится!
Пейсер
1
@Pacerier: на момент написания этого ответа это не так. Версии упоминаются в ответе. Я больше не слежу за PHP, поэтому не могу сказать, действительно ли он изменился в новой версии.
BalusC
2
Простой способ обмануть Apache из WinXP - добавить строку в файл hosts, указав, что IP-адрес сервера назначен другому домену, например: «127.0.0.1 mydomain.com». Я использовал это много раз, чтобы показать, как местный веб-сайт обманывает мою аудиторию, думая, что у меня есть интернет-соединение и сайт загружается очень быстро. Вы можете пойти другим путем и обмануть Apache, чтобы думать, что он работает локально, с "173.194.41.5 localhost", поэтому вы никогда не должны полностью доверять SERVER_NAME, если вы не уверены, что ваш Apache хорошо настроен.
Vicenteherrera
1
Я просто хочу добавить, что NGINX + PHP-FPM возвращает значение, установленное server_nameдирективой. Особенно, если не server_nameустановлено и _SERVER["SERVER_NAME"]будет пустым.
white_gecko
69

HTTP_HOSTцелевой хост, отправленный клиентом. Это может свободно управляться пользователем. Это не проблема, чтобы отправить запрос на ваш сайт с просьбой о HTTP_HOSTзначении www.stackoverflow.com.

SERVER_NAMEисходит из определения сервера VirtualHostи поэтому считается более надежным. Однако им также можно манипулировать извне при определенных условиях, связанных с настройкой вашего веб-сервера: см. Этот вопрос SO, в котором рассматриваются аспекты безопасности обоих вариантов.

Вы не должны полагаться ни на то, чтобы быть в безопасности. Тем не менее, что использовать на самом деле зависит от того, что вы хотите сделать. Если вы хотите определить, в каком домене работает ваш скрипт, вы можете безопасно использовать его, HTTP_HOSTесли недопустимые значения, исходящие от злонамеренного пользователя, не могут ничего сломать.

Пекка
источник
8
Да, но запрос, запрашивающий значение HTTP_HOST www.stackoverflow.com, будет отклонен большинством HTTP-серверов заранее, поэтому скрипт PHP даже не увидит запрос!
Pacerier
2
@Pacerier true, но не всегда, если сервер настроен неправильно.
Пекка
1
Как упоминалось в сообщении BalusC, при доступе к виртуальному хосту Apache по IP обе эти переменные содержат IP (по умолчанию), а не фактическое имя сервера. Вы должны использовать UseCanonicalName onв httpd.conf, чтобы заставить SERVER_NAMEбыть фактическим именем сервера.
Саймон Ист
@Pekka 웃, если сервер не настроен должным образом, $_SERVER['SERVER_NAME']также не будет работать . Плохо настроенный сервер будет установлен $_SERVER['SERVER_NAME']на основе значения Host:запроса клиента . Оба равны.
Pacerier
Хороший ответ, но я бы не предполагал виртуальный хостинг.
Энтони Ратледж
55

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

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

(По крайней мере, это то, что я заметил в виртуальных хостах на основе портов Apache)

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

Как уже отмечали другие, эти два также отличаются при использовании IPv6:

$_SERVER['HTTP_HOST'] == '[::1]'
$_SERVER['SERVER_NAME'] == '::1'
Саймон Ист
источник
2
Когда они исправят это коварное поведение?
Pacerier
27

Обратите внимание, что если вы хотите использовать IPv6, вы, вероятно, хотите использовать HTTP_HOSTвместо SERVER_NAME. Если вы введете http://[::1]/переменные окружения будут следующими:

HTTP_HOST = [::1]
SERVER_NAME = ::1

Это означает, что если вы сделаете mod_rewrite, например, вы можете получить неприятный результат. Пример для перенаправления SSL:

# SERVER_NAME will NOT work - Redirection to https://::1/
RewriteRule .* https://%{SERVER_NAME}/

# HTTP_HOST will work - Redirection to https://[::1]/
RewriteRule .* https://%{HTTP_HOST}/

Это относится ТОЛЬКО если вы обращаетесь к серверу без имени хоста.

Даниэль Маршалл
источник
1
SiteGround, в своем внутреннем http / https-коде перенаправления, используйтеhttps://%{SERVER_NAME}%{REQUEST_URI}
IXN
6

Если вы хотите проверить через server.php или что-то еще, вы должны вызвать его с помощью следующего:

<?php
    phpinfo(INFO_VARIABLES);
?>

или

<?php
    header("Content-type: text/plain");

    print_r($_SERVER);
?>

Затем получите доступ ко всем действительным URL-адресам вашего сайта и проверьте разницу.

stevewh
источник
5

Зависит от того, что я хочу узнать. SERVER_NAME - это имя хоста сервера, а HTTP_HOST - это виртуальный хост, к которому подключен клиент.

Роуланд Шоу
источник
4
Не совсем верно Rowland, SERVER_NAMEобычно это имя VirtualHost, а не сам сервер. А в Apache SERVER_NAMEчасто заполняется тем же значением, что и HTTP_HOST(см. Ответ BalusC).
Саймон Ист
1
@Simon, Так как хосты mosts теперь VirtualHost, что бы вы имели в виду под названием «сам сервер»?
Pacerier
Если вы используете виртуальный частный сервер (VPS) с одним веб-сайтом, вам не нужно предполагать, что это SERVER_NAMEотносится к виртуальному хосту. Однако можно по-прежнему использовать настройку виртуального хоста для одного сайта. Многие люди используют виртуальный хостинг, поэтому я понимаю вашу точку зрения.
Энтони Ратледж
2

Мне потребовалось некоторое время, чтобы понять, что люди подразумевают под « SERVER_NAMEболее надежным». Я использую общий сервер и не имею доступа к директивам виртуального хоста. Итак, я использую mod_rewrite, .htaccess чтобы отобразить разные HTTP_HOSTs в разные каталоги. В этом случае, это HTTP_HOSTимеет значение.

Ситуация аналогична, если использовать виртуальные хосты на основе имен: ServerNameдиректива внутри виртуального хоста просто говорит, какое имя хоста будет сопоставлено с этим виртуальным хостом. Суть в том, что в обоих случаях имя хоста, предоставленное клиентом во время request ( HTTP_HOST), должно совпадать с именем на сервере, которое само сопоставлено с каталогом. Независимо от того, выполняется ли сопоставление с директивами виртуального хоста или с правилами htaccess mod_rewrite, здесь второстепенно. В этих случаях HTTP_HOSTбудет так же, как SERVER_NAME. Я рад, что Apache настроен таким образом.

Однако ситуация с виртуальными хостами на основе IP отличается. В этом случае и только в этом случае, SERVER_NAMEи HTTP_HOSTмогут быть разными, потому что теперь клиент выбирает сервер по IP, а не по имени. Действительно, могут быть специальные конфигурации, где это важно.

Итак, начиная с этого момента, я буду использовать SERVER_NAME, на всякий случай, если мой код перенесен в эти специальные конфигурации.

Dominic108
источник
2

Предполагая, что у вас есть простая установка (CentOS 7, Apache 2.4.x и PHP 5.6.20) и только один веб-сайт (без виртуального хостинга) ...

В смысле PHP, $_SERVER['SERVER_NAME']это элемент, который PHP регистрирует в $_SERVERсуперглобале на основе вашей конфигурации Apache ( **ServerName**директива с UseCanonicalName On) в httpd.conf (будь то из включенного файла конфигурации виртуального хоста, что угодно, и т. Д.). HTTP_HOST является производным от hostзаголовка HTTP . Относитесь к этому как к пользовательскому вводу. Фильтруйте и проверяйте перед использованием.

Вот пример, где я использую $_SERVER['SERVER_NAME']в качестве основы для сравнения. Следующий метод взят из конкретного дочернего класса, который я создал named ServerValidator(child of Validator). ServerValidatorпроверяет шесть или семь элементов в $ _SERVER перед их использованием.

При определении, является ли HTTP-запрос POST, я использую этот метод.

public function isPOST()
{
    return (($this->requestMethod === 'POST')    &&  // Ignore
            $this->hasTokenTimeLeft()            &&  // Ignore
            $this->hasSameGETandPOSTIdentities() &&  // Ingore
            ($this->httpHost === filter_input(INPUT_SERVER, 'SERVER_NAME')));
}

Ко времени вызова этого метода все фильтрация и проверка соответствующих элементов $ _SERVER должны были произойти (и установить соответствующие свойства).

Линия ...

($this->httpHost === filter_input(INPUT_SERVER, 'SERVER_NAME')

... проверяет, что $_SERVER['HTTP_HOST']значение (в конечном счете полученное из запрошенного hostзаголовка HTTP) совпадает $_SERVER['SERVER_NAME'].

Теперь я использую суперглобальном Speak объяснить мой пример, но это только потому , что некоторые люди не знакомы с INPUT_GET, INPUT_POSTи INPUT_SERVERв отношении к filter_input_array().

Суть в том, что я не обрабатываю POST-запросы на моем сервере, если не выполнены все четыре условия. Следовательно, с точки зрения запросов POST, неспособность предоставить hostзаголовок HTTP (наличие проверено на более ранние версии) означает гибель для строгих браузеров HTTP 1.0 . Кроме того, запрашиваемый хост должен совпадать со значением для ServerNameв httpd.conf , и, расширением, значение для $_SERVER('SERVER_NAME')в $_SERVERсуперглобальный. Опять же, я буду использовать INPUT_SERVERфункции фильтра PHP, но вы поймете мой дрейф.

Имейте в виду , что Apache часто используется ServerNameв стандартных редиректов (например, оставляя слэш от URL - адрес: Пример, http://www.foo.com становится http://www.foo.com/ ), даже если вы не используя переписывание URL.

Я использую $_SERVER['SERVER_NAME']как стандарт, а не $_SERVER['HTTP_HOST']. Существует много вопросов по этому вопросу. $_SERVER['HTTP_HOST']может быть пустым, так что это не должно быть основой для создания соглашений кода, таких как мой публичный метод выше. Но только то, что оба могут быть установлены, не гарантирует, что они будут равны. Тестирование - лучший способ узнать наверняка (с учетом версии Apache и версии PHP).

Энтони Ратледж
источник
0

Как сообщает balusC, имя SERVER_NAME ненадежно и может быть изменено в конфигурации apache, конфигурации имени сервера для сервера и брандмауэра, которые могут находиться между вами и сервером.

Следующая функция всегда возвращает реальный хост (введенный пользователем хост) без порта, и это почти надежно:

function getRealHost(){
   list($realHost,)=explode(':',$_SERVER['HTTP_HOST']);
   return $realHost;
}
MSS
источник
0

$ _SERVER ['SERVER_NAME'] зависит от конфигурации ваших веб-серверов. $ _SERVER ['HTTP_HOST'] основан на запросе от клиента.

Виталие
источник