Pull to refresh

Comments 27

Незнаю как у остальных, раньше не интересовался, но у меня касательно этого «пунктик». Всегда провожу тестирование не только рабочего процесса, а и симулирую неверные запросы, чтобы сайт/приложение работал как часики.
Это интересно. Ещё есть забавный момент с передачей двух заголовков Host. В случае неправильной (той, которая по умолчанию, и в мануалах в интернетах) конфигурации nginx, он увидел первый заголовок, а на бэк-энд отправит второй.
Надо ли понимать это как то, что никто в интернете, в том числе сам Сысоев, не знает, как правильно настраивать nginx, а знаете только вы?

Ну так тогда поделитесь, что там неправильного и как должно быть.
Нет, это нужно понимать совершенно не так.

При использовании fastcgi, в файле fastcgi_params отсутствует установка заголовка Host. Должно быть:
fastcgi_param HTTP_HOST $host;

При использовании proxy_pass нужно обязательно указывать:
proxy_set_header Host $host;
В случае неправильной (той, которая по умолчанию, и в мануалах в интернетах) конфигурации nginx, он увидел первый заголовок, а на бэк-энд отправит второй.
Покажите мне такую конфигурацию, при которой на бэкенд будет отправлен второй заголовок.
На примере nginx + fastcgi + php-fpm устоит?

Берём fastcgi_params из wiki.nginx.org/PHPFcgiExample (соответствует конфигу по умолчанию)

В конфиге хоста пишем примерно следующее:
		server_name xenv.darkbyte.ru;
		location / {
			fastcgi_pass unix:/var/run/php5-fpm.sock;
			fastcgi_index index.php;
			include fastcgi_params;
		}


Скрипт index.php выводит некоторые переменные из массива $_SERVER, включая HTTP_HOST.

Делаем запрос:
# nc darkbyte.ru 80
GET / HTTP/1.1
Host: xenv.darkbyte.ru
Host: evil.com
Connection: close


В ответе видим: HTTP_HOST=evil.com

Т.е. nginx в переменной server_name видел xenv.darkbyte.ru, а PHP увидел уже evil.com.
Если добавить строку: fastcgi_param HTTP_HOST $host, то проблема решается, PHP видит тоже самое, что и nginx.
Это можно проверить, сделав такой же запрос, но в первом Host указать env.darkbyte.ru.

Если поменять заголовки местами, то будет 404, ибо evil.com у меня на сервере не объявлен.
Так и причем тут nginx? Параметры «HTTP_*» — это маппинг http-заголовков один к одному. Nginx совершенно справедливо передает все заголовки, что были в запросе, включая и оба заголовка «Host:». То, что php видит только последний — исключительно беда php, а может быть и беда конкретно php-fpm. В любом случае, программист, который обращается к HTTP_* должен ожидать там всё, что угодно.

Посмотрите тем же tcpdump-ом хотя бы, что именно nginx отправил.
Возможно оно так и есть, но ведь решить проблему можно только на уровне nginx. И в конфиге по умолчанию, который так же предлагается во многих мануалах по настройке nginx+php-fpm, этот момент упущен.

А tcpdump умеет слушать файловые сокеты? Если нет, то чем можно? Сходу не смог найти подходящей утилиты.
1. Что у вас за приложение такое, что ему нужен HTTP_HOST? Взяли вы netcat и специально послали два заголовка, получили 404. Нормальные браузеры так себя не ведут. В чем состоит проблема?

Оставьте с хостами разбираться веб-серверу, не надо этим в приложении заниматься. Сегодня Host, а завтра ещё какой-нибудь заголовок. Я так подозреваю, что эффект будет одинаковый в отношении любого заголовка: php-скрипт увидит только последний из присланных. Содержимое HTTP_ — это в любом случае данные полученные от пользователя, и программист, который не выучил, что нельзя доверять данным присланным пользователем — просто некомпетентен. Возможно, что это повод вообще отказаться от такого приложения, безоспасность которого опирается на HTTP_*.

2. fastcgi_params содержит исключительно подборку переменных окружения из RFC 3875 и не более.

3. Каждый второй школьник, впервые в жизни настроивший что-то на локалхосте, скорее пишет мануал или руководство. Чему тут удивляться? 9 из 10 плохи в той или иной степени, отнюдь не из-за HTTP_HOST.

А tcpdump умеет слушать файловые сокеты? Если нет, то чем можно? Сходу не смог найти подходящей утилиты.
Можно strace-ом посмотреть.
Перенастроил fastcgi на обычные сокеты, посмотрел трафик — действительно, передаётся оба заголовка HTTP_HOST. Но опять таки, то, что nginx и php в разном порядке рассматривают заголовки — это не правильно, как мне кажется.

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

Конечно браузер не будет посылать два заголовка Host, но ведь не только браузеры заходят на сайты. Внимание на данную проблему я обратил после того, как на одном сайте обнаружил «хитрый» механизм входа в админку, основанный на домене. Авторизация для входа на поддомен админки была настроена в nginx, а проверка домена в самом приложении.
И если есть возможность на уровне веб сервера хоть немного прикрыть дыры приложений, которые за ним прячутся, то я не упускаю такой возможности.

Да, но наверное не стоит при этом упрекать всех, кто использует nginx просто по прямому назначению, а не для валидации входных данных ради небезопасных приложений. Возможности nginx в этом смысле достаточно велики даже из коробки. Можно понаписать регулярок, и ещё проверять $args на SQL инъекции. А особо озабоченные проблемой люди даже специальный модуль разработали. Но это палка о двух концах.

Я даже специально ещё раз подчеркну, что вообще присутствие HTTP_HOST среди переменных окружения никто не обещал. Попытка её активно использовать — это уже нестандарт, и если вам действительно нужно как-то пробрасывать относительно валидный хост на бэкенд и вы по каким-то причинам очень не желаете использовать SERVER_NAME (специально для того предназначенную, и описанную в спецификации с словом MUST), то лучше для этого придумать какую-нибудь свою переменную, не пересекающуюся с заголовками:
fastcgi_param MY_HOST xenv.darkbyte.ru; — будет намного правильнее.
Хорошо, когда за nginx находятся свои приложения, которым можно доверять. Но бывает, что ставятся чужие решения, и не всегда они достаточно качественные, чтобы просто их поставить и забыть.

Пример с авторизацией через поддомен — это частный случай. Но можно взять более распространённый пример — сбор статистики средствами самого движка. Очень часто приходится сталкиваться с тем, что переменной HTTP_HOST доверяют, считая, что раз веб сервер отправил на их сайт запрос, значит в HOST будет точно их хост.

Опять же, одно дело дорогие регулярки и модули, а другое дело, практически бесплатный костыль :)

В моём случае, в server_name написано "~^((.*)\.)*(.+\..+)$", а валидный хост передаётся отдельным заголовком.
Вот интересно почему нет массовой популярности у lighttpd как у nginx? Неужели последний гораздо круче?
Очень аргументировано.
немного странно вообще сравнивать эти две вещи — nginx мощен тем что поддерживает всяко кэширование, прокси, балансировку, и почти всё что умеет апач.

а lighttpd — это веб-сервер — который поддерживает те технологии, которые нужны современному лёгкому веб-серверу.
>> а lighttpd — это веб-сервер — который поддерживает те технологии, которые нужны современному лёгкому веб-серверу.
Что не мешает ему также поддерживать «кэширование, прокси, балансировку, и почти всё что умеет апач.»
Разработка lighttpd 1.x прекращена много лет назад (только иногда багфиксы случаются). А релиз lighttpd 2.0 так и не увидел свет.
Первое что приходит в голову: передать неправильный заголовок Host:, состоящий из правильного имени хоста, нулевого байта и мусора.
Спасибо, добавил в основной текст в качестве «Запроса 19».
На третьем запросе сервера «посыпались». И нет ни одного ответа «HTTP/1.0 200 OK».
И не должно быть.

Остальные же результаты тестов зависят от настройки самих серверов, каждый волен настраивать как того желает.

Исключение составили DevConf, e-Legion Ltd. и Intel. Первые два используют nginx, поэтому проблема, скорее всего, именно в его настройке
Нет, проблема может быть, и скорее всего, в бэкенде. Nginx сам никогда не выдаст INTERNAL SERVER ERROR в верхнем регистре.
На третьем запросе сервера «посыпались». И нет ни одного ответа «HTTP/1.0 200 OK».

И не должно быть.

Я пожалуй даже поясню этот пункт, ибо видимо не все понимают.

Кто-то видимо ошибочно полагает, что указываемая в запросе или ответе версия протокола — это версия протокола запроса или ответа (использумая при формировании самого запроса или ответа). Нет, это заблуждение.

Этот механизм нужен, чтобы сервер и клиент сообщили друг-другу наивысшую версию протокола, с которой они совместимы. Если клиент отправил HTTP/1.0 запрос, то nginx не будет использовать ничего в ответе, что могло бы оказаться несовместимым с HTTP/1.0 (такие вещи, например, как chunked encoding).

Подробнее читайте в RFC 2145.

P.S. Кстати, не стоит ещё удивляться также, что в запросах без хоста (в строке запроса или заголовке Host) в принципе не работает виртуальный хостинг.
> На третьем запросе сервера «посыпались». И нет ни одного ответа «HTTP/1.0 200 OK».

Я там отдаю 403 Forbidden. Сервер не должен отвечать на запрос, в котором нет корректного хоста. См. DNS Rebinding.
Неправильно.

HTTP/1.1 — это не 403 Forbidden, а именно 400 Bad Request. Потому, что это неправильно составленный запрос
В HTTP/1.0 заголовок Host необязателен, так что GET / HTTP/1.0 без Host — вполне валидный запрос
С точки зрения стандартов Вы правы.

Но это ещё и вопрос безопасности. Во избежание атак DNS Rebinding запросы, не направленные на правильный хост (или его поддомены), не должны отдавать ничего хорошего. Иначе это потенциальная дыра.

Другой вопрос — будут ли клиенты, умеющие только HTTP/1.0, кэшировать результаты DNS-запросов (тем самым добавляя «клиентскую сторону» уязвимости). Мы никак не можем гарантировать, что не будут. Даже если клиент этого не делает, может влезть nscd или что-то в этом роде.

Статический сайт с общедоступной информацией и отсутствием логина/форм/скриптов может от DNS Rebinding и не защищаться. Перечисленные в Вашем посте сервисы — обязаны.
Во-первых, пост не мой.

Во вторых, кэширование dns-ответов на клиентской системе как раз наоборот блокирует атаку. Если у меня ответ 192.0.2.12 закэшировался в браузере, атакующий может хоть кол на голове dns-сервера тесать и подставлять в зону любые адреса, хоть 127.0.0.1 — мой браузер будет обращаться туда, куда закэшировал.

В третьих, те серверы, которые в реальности могли бы пострадать, не удастся настроить таким образом. Я имею ввиду веб-интерфейсы, которые действительно находятся в локальной сети: модемы, nas, точки доступа, веб-камеры и прочее подобное оборудование. Однако, чтобы с ними что-то сделать, нужно авторизоваться, что сделать будет сложно.

А в четвёртых, те веб-серверы, которые всё-таки можно так настроить, обычно находятся не в локальной сети, к ним можно сделать запрос без Host или с неверным Host и безо всякой DNS Rebinding. Они могут в ответ на HTTP/1.0 без Host: выдавать не 403, а 302, 204, 205, или даже 200 с объяснением, что происходит — мы получим полное соответствие протоколу и отсутствие возможности такой атаки. В моём случае клиент обычно увидит default virtualhost, который говорит «It works!» — какая уж тут уязвимость.
Sign up to leave a comment.

Articles