PHP $ SERVER ['HTTP HOST'] против $ 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 ответов


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

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

как я отметил ниже, 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);
}

использовать. Они оба одинаково (в)безопасны, так как во многих случаях ИМЯ_СЕРВЕРА просто заполняется из HTTP_HOST. Обычно я использую HTTP_HOST, чтобы пользователь оставался на точном имени хоста, с которого он начал. Например, если у меня такой же сайт на a .com and .org domain, я не хочу отправлять кого-то из .орг .com, особенно если у них могут быть маркеры входа в систему .организация, которую они потеряют, если отправят в другой домен.

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

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

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

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

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

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

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

  • жестко закодированный исходный код

  • внешний запрос (скручиваемость)

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

  • etc

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

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

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

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

С UseCanonicalName Off имя сервера происходит от содержимого Host: заголовок в запросе. С UseCanonicalName DNS это происходит от обратного поиска DNS IP-адреса виртуального хоста. Прежняя настройка используется для name-based динамический виртуальный хостинг, причем последний используется для * * 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']