PHP $ _SERVER ['HTTP_HOST'] против $ _SERVER ['SERVER_NAME'], правильно ли я понимаю справочные страницы?

126

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

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

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

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

Комментарии для последующего просмотра:

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

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

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

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

Теги:
security
owasp

8 ответов

113
Лучший ответ

Это, наверное, все сначала подумали. Но это немного сложнее. См. статья Криса Шифлетта SERVER_NAME Versus 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
    Интересно, я никогда не знал, что SERVER_NAME по умолчанию использует введенные пользователем значения в Apache.
Показать ещё 3 комментария
52

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

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

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

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

  • 4
    Примечание: порт отсутствует в HTTP_HOST для 443 (порт SSL по умолчанию).
  • 0
    Другими словами, значение HTTP_HOST не является параметром Host: предоставленным пользователем. Это просто основано на этом.
Показать ещё 1 комментарий
21

Это подробный перевод того, что 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, который пытается получить имя хоста всеми возможными способами в порядке лучшей практики:

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);
}
  • 0
    этот код вредит моему мозгу! .. пожалуйста, ради любви к бабочкам, не пишите свой код таким образом! (извините за мой английский)
  • 1
    @StefanNch Пожалуйста, определите «этот путь».
Показать ещё 10 комментариев
19

Используйте либо. Они оба одинаково безопасны, так как во многих случаях SERVER_NAME просто заполняется из HTTP_HOST. Обычно я запускаю HTTP_HOST, так что пользователь остается на том точном имени хоста, с которого они начали. Например, если у меня есть тот же сайт в домене .com и .org, я не хочу отправлять кого-то из .org в .com, особенно если у них могут быть маркеры входа в .org, которые они потеряли бы, если бы отправили другой домен.

В любом случае вам просто нужно быть уверенным, что ваш webapp будет реагировать только на хорошо известные домены. Это может быть выполнено либо (a) с проверкой на стороне приложения, как Gumbo, либо (b) с использованием виртуального хоста на доменных именах, которые вы хотите, чтобы не отвечал на запросы, которые дают неизвестный заголовок Host.

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

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

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

  • 0
    Оставаться с решением (a), (b) на самом деле небезопасно, использование абсолютного URI в HTTP-запросах позволяет обойти защиту виртуальных хостов на основе имен. Поэтому настоящим правилом никогда не является доверие к SERVER_NAME или HTTP_HOST.
  • 0
    @bobince, как работает упомянутый поисковик угон? Поисковые системы сопоставляют слова с URL-адресами доменов, они не имеют дело с IP-адресами. Так почему же вы говорите, что «злоумышленник может заставить поисковые системы рассматривать attacker.com как лучший первоисточник IP вашего сервера»? Похоже, это ничего не значит для поисковых систем. Что это вообще собирается делать?
Показать ещё 2 комментария
7

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

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

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

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

  • 0
    Понял. У меня зависание "как пользователь может изменить значение $ _SERVER ['HTTP_HOST']?" Это вообще возможно?
  • 4
    Пользователь может изменить это, потому что это только содержимое заголовка Host из входящего запроса. Главный сервер (или VirtualHost, связанный с значением по умолчанию : 80) будет отвечать на все неизвестные хосты, таким образом, содержимое тега Host на этом сайте может быть установлено на что угодно.
Показать ещё 2 комментария
4

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

Да, safe использовать $_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!';
// ...

The advantage of $_SERVER['HTTP_HOST'] is that its behavior is more well-defined than $_SERVER['SERVER_NAME']. Contrast ➫➫:

Содержимое заголовка Host: из текущего запроса, если он есть.

с:

Имя хоста сервера, под которым выполняется текущий script.

Using a better defined interface like $_SERVER['HTTP_HOST'] means that more SAPIs will implement it using reliable well-defined behavior. (Unlike the other.) However, it is still totally SAPI dependent ➫➫:

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

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

  • локальный файл конфигурации

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

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

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

  • запрос клиента/злоумышленника Host:

  • и т.д.

Usually its done via the local (SAPI) config file. Note that you have configured it correctly, e.g. in Apache ➫➫:

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

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

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

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

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

2

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

Добавить SetEnv MyHost domain.com в .htaccess файл на root и добавить код в Common.php

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

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

  • 0
    Конечно, можно обойти DNS. Злоумышленник может просто выдать поддельное значение Host: напрямую IP-адресу вашего сервера.
1

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню