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
вары ":
http://markjaquith.wordpress.com/2009/09/21/php-server-vars-not-safe-in-forms-or-links/
http://php.net/manual/en/reserved.variables.server.php (комментарий: Владимир Корнеа, 14 марта 2009 г., 01:06)
Видимо, речь идет в основном о $_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']