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

Я много искал, а также читал документацию по PHP $ _SERVER. Имею ли я это право в отношении того, какие скрипты 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-атаках, даже при использовании в формах.

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

9 ответов

Решение

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • так далее

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

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

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

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

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

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

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

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

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

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

Я не уверен и не очень доверяю $_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 пришел из другого домена.

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

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

Другие вопросы по тегам