Re: Об одной малоизвестной уязвимости в веб сайтах

Maxim Dounin mdounin at mdounin.ru
Tue Jun 17 07:00:31 UTC 2014


Hello!

On Mon, Jun 16, 2014 at 03:36:38PM -0400, S.A.N wrote:

> > > Maxim Dounin Wrote:
> > > -------------------------------------------------------
> > > > Какая-либо проблема появляется тогда и только 
> > > > тогда, когда администратор начинает писать в конфиге $http_host, 
> > > > не думая о последствиях.  Есть мнение, что совсем простое решение 
> > > > этой проблемы - не делать так (c) анекдот.
> > > 
> > > Вся проблема в том что сами программисты Nginx в модуле FastCGI
> > используют
> > > переменную $http_host для HTTP_HOST, вместо нормализованной
> > переменой $host,
> > > администраторам на оборот приходится исправлять это своими руками и
> > писать в
> > > конфиге
> > > fastcgi_param  HTTP_HOST  $host;
> > > Если следовать вашей логике, надо изменить код модуля FastCGI, чтобы
> > по
> > > умолчанию Nginx был вполне себе безопасный.
> > 
> > По умолчанию в fastcgi http-заголовки передаются как есть, в виде 
> > параметров HTTP_*.  Каноническое же имя сервера доступно в 
> > параметре SERVER_NAME.  Что из этого и как использовать - это 
> > вопрос к приложению, а не к nginx'у.
> 
> Я знаю, и уже выше объяснял почему бекенд-приложениям нужно использовать
> HTTP_HOST вместо SERVER_NAME.
> Значения SERVER_NAME не может использоваться, если в директиве server_name
> используется маска или бекенд обрабатывает запросы для default_server.

Если SERVER_NAME нельзя использовать - значит, необходимо передать 
дополнительный параметр, который и использовать.

Использовать HTTP_* поля для чего-то, что позволяет "получить 
доступ" - это неправильно.

> Ещё раз приведу пример, в котором мы легко можем получить доступ, к закрытым
> на уровне Nginx ресурсам.
> 
> server
> {
>   server_name *.example.com;
>   ...
>   fastcgi_pass php;
> }
> 
> server
> {
>   server_name private.example.com;
> 
>   location / {
>       allow 192.168.1.0/24;
>       deny all;
> 
>       fastcgi_pass php;
> 
>       auth_request /auth;
>     }
>     ...
>  }
> 
> Как видно два хоста используют один upstream, на котором бекенд приложения
> даёт расширенные привилегии юзерам private.example.com, в расчете на то что
> Nginx предварительно сделал проверку по IP и успешно провел аунтификацию,
> всё вроде логично и удобно сделано, но такая схема легко ломается таким
> запросом.
> 
> GET http://example.com/SecureData/ HTTP/1.1
> Host: private.example.com
> 
> Бекенд получает HTTP_HOST=private.example.com, даёт юзеру привилегии, но
> Nginx не проводил проверки по IP и не делал запрос аунтификацию, потому что
> отработал хост конфигурации для example.com вместо private.example.com

Проблема в том, что приложение некорректно предполагает, что 
HTTP_HOST=private.example.com чем-то отличается от других.  Как 
показывает пример запроса выше - это не так.  И "не так" - не 
только в nginx'е, но и в других серверах.

Не надо себя обманывать и пытаться закрыть nginx'ом небезопасную 
логику приложения - это не работает и рано или поздно выстрелит.

Правильное решение - передавать информацию о произошедшей 
авторизации явно и отдельно (или пользоваться параметром 
SERVER_NAME, который уже передаётся и предназначен специально для 
идентификации сервера).

> В данном случаи можно было бы использовать переменную SERVER_NAME, но я выше
> писал почему это не всегда возможно.
> 
> Если мы оба понимаем, что ситуация когда authority component и значения Host
> разные, это исключительная ситуация и её нужно обрабатывать как Exception, в
> этом случаи есть три варианта поведения, выбросить Exception (отдать 400
> статус), исправить ошибку (использовать переменую $host), и самый плохой
> вариант (антишаблон) ничего не делать и не обрабатывать эту исключительную
> ситуацию и оставить все как есть.

Возможно, когда-нибудь мы и придём к тому, что в таких ситуациях 
будет возвращаться 400.  Но это ни коим образом не избавляет от 
необходимости исправить приложение.

-- 
Maxim Dounin
http://nginx.org/



Подробная информация о списке рассылки nginx-ru