2009-09-22 3 views
130

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

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

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

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

Followup комментарии:

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

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

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

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

ответ

120

Это, наверное, первая мысль каждого. Но это немного сложнее. См. Chris Shiflett’s article SERVER_NAME Versus HTTP_HOST.

Кажется, что нет серебряной пули. Только когда вы force Apache to use the canonical name, вы всегда получите правильное имя сервера с 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

Lol, я прочитал эту статью и на самом деле не ответил на мой вопрос. Какой из них использует pro devs? Если и есть. – Jeff

+2

Iiiiinteresting, я никогда не знал, что SERVER_NAME использовал значения, предоставленные пользователем, по умолчанию в Apache. – Powerlord

+0

@Jeff. Для серверов, на которых размещено более одного суб/домена, у вас есть только два варианта: '$ _SERVER ['SERVER_NAME']' и '$ _SERVER ['HTTP_HOST']' (помимо реализации другого пользовательского рукопожатия на основе пользовательский запрос). Про разработчики не доверяют вещам, которые они не понимают полностью. Таким образом, они либо имеют свою настройку SAPI ** совершенно правильно ** (в этом случае опция, которую они используют *, * даст правильный результат), либо они сделают белый список таким образом, что не имеет значения, какие значения дает SAPI. – Pacerier

7

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

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

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

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

+0

Понял. Мое зависание «как пользователь может изменить значение $ _SERVER ['HTTP_HOST']? Возможно ли это? – Jeff

+3

Пользователь может изменить это, потому что это только содержимое заголовка хоста из входящего запроса. Основной сервер (или VirtualHost, связанный с __default __: 80) будет отвечать на все неизвестные хосты, поэтому содержимое тега Host на этом сайте может быть настроено на что угодно. – Powerlord

+3

Обратите внимание, что виртуальные хосты на основе IP будут ВСЕГДА реагировать на свой конкретный IP-адрес, поэтому вы ни при каких обстоятельствах не можете ** доверять HTTP-хосту. – Powerlord

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.

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

+0

Пребывание с решением (a), (b) не очень безопасно, использование абсолютного URI в HTTP-запросах позволяет использовать байпас безопасности виртуальных хостов на основе имени. Таким образом, реальное правило: ** никогда не доверять SERVER_NAME или HTTP_HOST. – regilero

+0

@bobince, Как работает вышеупомянутый поисковый механизм? Поисковые системы сопоставляют слова с доменом ** urls **, они не имеют отношения к IP-адресам. Итак, почему вы говорите, что «злоумышленник может заставить поисковые системы видеть« атакующий »как лучший первичный источник для IP-адреса вашего сервера? Это ничего не значит для поисковых систем. Что это будет делать? – Pacerier

+1

Google, безусловно, имел (и, вероятно, все еще имеет в какой-то форме) концепцию обманных сайтов, так что, если ваш сайт доступен как http: // example.com/',' http: // www.example.com/ ' и 'http: // 93.184.216.34 /' он объединил бы их в один сайт, выберет самый популярный из адресов и вернет ссылки только на эту версию. Если бы вы могли указать «evil-example.com» на тот же адрес и заставите Google на короткое время увидеть, что в качестве более популярного адреса вы можете украсть сок сайта. Я не знаю, насколько это практично сегодня, но я видел, как российские злоумышленники пытались это сделать в прошлом. – bobince

2

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

SetEnv MyHost domain.com Добавить в файл .htaccess на корне и добавить THS код в common.php

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

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

+0

Конечно, можно обойти DNS. Злоумышленник может просто выдать fradulent значение «Host:» непосредственно на IP-адрес вашего сервера. – Pacerier

22

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

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

этот код болит мой мозг! .. пожалуйста, за любовь к бабочкам, не пишите свой код таким образом! (извините мой engrish) – StefanNch

+1

@StefanNch Пожалуйста, определите «этот путь». – showdev

+1

@showdev Я действительно нахожу «трудным» для чтения условия, например 'if ($ host = $ _SERVER ['HTTP_X_FORWARDED_HOST'])' или 'x = a == 1? Правда: ложь. В первый раз, когда я увидел это, мой мозг искал экземпляр $ host и ответ за «почему только один» = «знак?». Я начинаю не любить слабые языки программирования. Все написано по-разному. Вы не экономить время, и вы не особенный. Я не пишу код таким образом, потому что по прошествии времени я должен его отлаживать. Выглядит очень грязно для усталого мозга! Я знаю, что мой английский украшен, но, по крайней мере, я стараюсь. – StefanNch

56

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

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

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

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

+3

Примечание: Порт отсутствует в HTTP_HOST для 443 (порт SSL по умолчанию). – Mike

+0

Иными словами, значение 'HTTP_HOST' не является параметром' Host: ', предоставленным пользователем. Это просто основано на этом. – Pacerier

1

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

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!'; 
// ... 

Преимущества $_SERVER['HTTP_HOST'] в том, что его поведение более четко определенное, чем $_SERVER['SERVER_NAME']. Контраст ➫➫:

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

с:

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

Использование более четко определить интерфейс, как $_SERVER['HTTP_HOST'] означает, что больше SAPIs будет осуществлять его с помощью надежного вполне определенного поведения. (В отличие от the other.) Тем не менее, он по-прежнему полностью зависит от 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 используется вместо этого.

Смежные вопросы