PHP $ _SERVER ['HTTP_HOST'] против $ _SERVER ['SERVER_NAME'], правильно ли я понимаю справочные страницы? - PullRequest
147 голосов
/ 22 сентября 2009

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

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

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

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

Последующие комментарии:

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

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

Мой вывод о моем первоначальном вопросе выше состоит в том, что «1036» безопасно использовать для всех ссылок на сайте, не беспокоясь о XSS-атаках, даже когда они используются в формах.

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

Ответы [ 9 ]

137 голосов
/ 22 сентября 2009

Это, наверное, первая мысль всех. Но это немного сложнее. См. Статью Криса Шифлетта 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;
}
62 голосов
/ 21 августа 2012

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

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

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

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

24 голосов
/ 18 января 2012

Это подробный перевод того, что 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);
}
24 голосов
/ 22 сентября 2009

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

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

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

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

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

9 голосов
/ 06 марта 2015

Безопасно ли использовать $_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']. Контраст & # x27ab; & # x27ab; :

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

с:

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

Использование более определенного интерфейса, такого как $_SERVER['HTTP_HOST'], означает, что больше SAPI будут реализовывать его, используя надежное четко определенное поведение. (В отличие от другого .) Однако он все еще полностью зависит от SAPI & # x27ab; & # x27ab; :

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

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

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

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

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

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

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

  • и т.д.

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

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

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

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

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

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

8 голосов
/ 22 сентября 2009

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

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

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

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

2 голосов
/ 15 марта 2010

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

Добавьте SetEnv MyHost domain.com в файл .htaccess в корневом каталоге и добавьте этот код в Common.php

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

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

1 голос
/ 10 января 2014

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

0 голосов
/ 26 февраля 2019

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