Ещё хочу добавить — проблема с переопределением вышестоящих настроек встречается не только у add_header, но и у error_page.
А так же у proxy_set_header. Думаю, что список таких директив можно продолжить.
А давайте продолжим список! Главное, что бы это могло привести к проблемам безопасности.
К примеру, с proxy_set_header я могу придумать кейс в котором это может стать проблемой (хоть пока и не встречал). А вот не сознательное переопределение error_page не выглядит чем-то серьезным.
Хотелось бы только узнать на сколько она применима в случае если у меня на серверах стоит какая-нибудь панель управления хостингом типа ИСП\Адженти\Весты\Плеска?
Честно говоря, ничего не могу сказать по этому поводу. Я не большой сторонник панелей управления, поэтому опыта работы с ними крайне мало. Быть может, кто-то с большим опытом подскажет как правильно сделать интеграцию, какие существуют подводные камни и вот это все. А может и сделает это самостоятельно ;-)
Точного roadmap пока нет, примерные планы выглядят так:
— улучшить работу с переменными, добавить новых директив которые могут их предоставлять. Например, сейчас нет поддержки server_name :(
— продолжить улучшать работу с регулярками, что мы можно было делать более хитрые проверки. Кстати, я его обернул в самостоятельное приложение (бывает полезно, когда нужно быстренько проверить регулярочку): Regex Ninja
— разобраться с разными стьюпидами и написать хорошую документацию
Ну и, конечно же, написать новых проверок:)
Например, для Open Redirect'ов в реврайтах:
У меня нет ответа на вопрос, почему if используется чаще, чем map. Map, конечно же, тоже используется для валидации, но заметно реже:( Я это вижу как в Яндексе так и за его пределами.
Именно поэтому я и начал с валидации регулярок в if. В будущем планирую добавить проверки для map (там будут примерно те же проблемы) и возможно добавить какую-то рекомендательную проверку по переписыванию if на map. Тут есть над чем подумать:)
Должен попросить у вас прощения. Скучное решение будет работать не во всех кейсах (в частности если в base64-кодированной строке будет "/").
Лучшим решением проблемы с OCSP Stapling'ом и WoSign — обновить nginx 1.7.11 (можно и ниже, просто не имеет смысла, ибо если уж обновлять до mainline то до текущего), там судя по коду все должно завестить без патчей и костылей (если не вылазить за 255 байт в закодированном OCSP-запросе) :-)
Мне кажется, что вы просто этого не замечаете за счет некого везения:-) И поэтому у вас в тестах иногда все хорошо (в ваших), а иногда (как писали выше в комментах) плохо:-)
Вот сейчас для вашего домена у меня получается запрос к WoSign без доп. символов "/+=" (подлежащий кодированию при NGX_ESCAPE_URI_COMPONENT) алфавита base64, поэтому все и работает:
Поэтому за счет периодического везения у вас сложилось впечатление что все работает, т.к. nginx кеширует валидный ocsp-ответ на 1 час, а не валидный на 5 минут.
Тоже решил завести OCSP Stapling для на своем тестовом сервере для WoSign, но ничего не помогало.
nginx в error.log'е ругался на не правильный тип ответа:
2015/03/27 21:07:18 [error] 21722#0: OCSP responder sent invalid "Content-Type" header: "text/html" while requesting certificate status, responder: ocsp6.wosign.com
Не долго думая, решил посмотреть на трафик к OCSP Responder'у:
HTTP-based OCSP requests can use either the GET or the POST method to
submit their requests. To enable HTTP caching, small requests (that
after encoding are less than 255 bytes) MAY be submitted using GET.
If HTTP caching is not important or if the request is greater than
255 bytes, the request SHOULD be submitted using POST. Where privacy
is a requirement, OCSP transactions exchanged using HTTP MAY be
protected using either Transport Layer Security/Secure Socket Layer
(TLS/SSL) or some other lower-layer protocol.
An OCSP request using the GET method is constructed as follows:
GET {url}/{url-encoding of base-64 encoding of the DER encoding of
the OCSPRequest}
where {url} may be derived from the value of the authority
information access extension in the certificate being checked for
revocation, or other local configuration of the OCSP client.
An OCSP request using the POST method is constructed as follows: The
Content-Type header has the value «application/ocsp-request», while
the body of the message is the binary value of the DER encoding of
the OCSPRequest.
С виду запрос от nginx полностью соответствует RFC. Немного поиграем:
# Пробуем запрос полученный от nginx
$ http -v http://ocsp6.wosign.com/ca6/server1/free/MFEwTzBNMEswSTAJBgUrDgMCGgUABBSgZmHxbLzCPpi8cZFIMLhaqo0KawQU0qcWIHyv2ZWe60MKGfLguXQOqMcCEEbxnVuQIuYg4cpCNpVclHY%3d
GET /ca6/server1/free/MFEwTzBNMEswSTAJBgUrDgMCGgUABBSgZmHxbLzCPpi8cZFIMLhaqo0KawQU0qcWIHyv2ZWe60MKGfLguXQOqMcCEEbxnVuQIuYg4cpCNpVclHY%3d HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Host: ocsp6.wosign.com
User-Agent: HTTPie/0.9.2
HTTP/1.1 502 Bad Gateway
Connection: keep-alive
Content-Length: 173
Content-Type: text/html
Date: Sat, 28 Mar 2015 09:14:25 GMT
Server: nginx/1.0.15
<html>
<head><title>502 Bad Gateway</title></head>
<body bgcolor="white">
<center><h1>502 Bad Gateway</h1></center>
<hr><center>nginx/1.0.15</center>
</body>
</html>
# Пробуем запрос без урлкодирвоания
$ http -v http://ocsp6.wosign.com/ca6/server1/free/MFEwTzBNMEswSTAJBgUrDgMCGgUABBSgZmHxbLzCPpi8cZFIMLhaqo0KawQU0qcWIHyv2ZWe60MKGfLguXQOqMcCEEbxnVuQIuYg4cpCNpVclHY\=
GET /ca6/server1/free/MFEwTzBNMEswSTAJBgUrDgMCGgUABBSgZmHxbLzCPpi8cZFIMLhaqo0KawQU0qcWIHyv2ZWe60MKGfLguXQOqMcCEEbxnVuQIuYg4cpCNpVclHY= HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Host: ocsp6.wosign.com
User-Agent: HTTPie/0.9.2
HTTP/1.0 200 OK
Connection: close
Content-Length: 1514
Content-Transfer-Encoding: Binary
Content-Type: application/ocsp-response
Date: Mar 28 09:14:29 2015 GMT
Expires: Mar 30 09:14:29 2015 GMT
+-----------------------------------------+
| NOTE: binary data not shown in terminal |
+-----------------------------------------+
# Пробуем отправить POST запрос вместо GET
$ http -v post http://ocsp6.wosign.com/ca6/server1/free/ < ocsp
POST /ca6/server1/free/ HTTP/1.1
Accept: application/json
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: 83
Content-Type: application/json
Host: ocsp6.wosign.com
User-Agent: HTTPie/0.9.2
+-----------------------------------------+
| NOTE: binary data not shown in terminal |
+-----------------------------------------+
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 1514
Content-Transfer-Encoding: Binary
Content-Type: application/ocsp-response
Date: Sat, 28 Mar 2015 09:14:37 GMT
Expires: Mar 30 09:14:37 2015 GMT
Server: nginx/1.0.15
+-----------------------------------------+
| NOTE: binary data not shown in terminal |
+-----------------------------------------+
Хм, любопытно. Похоже на расхождение логики nginx и OCSP Responder'а WoSign — первый считает OCSPRequest компонентом URI, поэтому закодировал не только основные разделители но и дополнительные (= в нашем случае), вторые же на это видимо не расчитывают.
Я нашел два варианта решения этой проблемы:
1. Скучный — исправить урл. Просто заменить ssl_stapling_responder на свой и с него уже запроксировать на ocsp6.wosign.com.
Добавляем сервер:
Выглядит работоспособным. Тест так же говорит что OCSP Stapling работает
2. Интересный:) Будучи программистом — мне больше нравится исправлять код, нежели раставлять костыли в конфигах. Поэтому я сделал простой патч gist.github.com/buglloc/6f14a16ab702478d23e3 который добавляет булевую опцию ssl_stapling_force_post по которой nginx начинает делать POST запрос вместо GET.
$ echo QUIT | openssl s_client -connect www.buglloc.com:443 -status 2> /dev/null | grep -A 17 'OCSP response:'
OCSP response:
======================================
OCSP Response Data:
OCSP Response Status: successful (0x0)
Response Type: Basic OCSP Response
Version: 1 (0x0)
Responder Id: C = CN, O = WoSign CA Limited, CN = WoSign Free SSL OCSP Responder(G2)
Produced At: Mar 28 10:04:05 2015 GMT
Responses:
Certificate ID:
Hash Algorithm: sha1
Issuer Name Hash: A06661F16CBCC23E98BC71914830B85AAA8D0A6B
Issuer Key Hash: D2A716207CAFD9959EEB430A19F2E0B9740EA8C7
Serial Number: 46F19D5B9022E620E1CA4236955C9476
Cert Status: good
This Update: Mar 28 10:04:05 2015 GMT
Next Update: Mar 30 10:04:05 2015 GMT
Надеюсь кому-то поможет. Я старался обойтись минимальными правками и вроде достаточно внимательно поправил код. Конечно, скорее более правильно было бы сделать как современные браузеры (которые славятся своей «приспосабливаемостью»;) — они зачастую сначала шлют GET и в случае провала пытаются сделать POST запрос. Но патч получился бы менее легким и читабельным, поэтому я не стал этого делать.
Я говорю про дефолтные конфиги непосредственно PHP, что при стандартном stack size в 8-10M и дефолтном pcre.recursion_limit в 100000 в сегфолт от подобных регулярок в КОДЕ php уходит одинаково хорошо как php-fpm так и php в виде модуля к apache. Неправильно приготовить php-fpm можно скорее не конфигами самого php-fpm (как вы правильно подметили о количестве параметров) а настройками php, к примеру не отключить user_ini.filename или cgi.fix_pathinfo или еще не одним параметр специфичный и важный непосредственно в контексте php-fpm.
В точку! Не однократно вижу целые группы разработчиков, которые не в состоянии ответить нужен ли им какой либо php-модуль и приходтся открывать список функций и шерстить сорцы, а между тем их зоопарк это потенциальные баги, уязвимости и т.д. и т.п.
Для php-fpm проблем не меньше, он так же легко уходит в сегфолт, например, при дефолтных конфигах и рекурсивных регулярках (#([a-z])+# к примеру). Я бы сказал 50/50, уходя от монстроидального apache с его порой причудливыми конфигами (в более чем распространенных дистрах) к php-fpm ведет к проблеме его правильного приготовления, т.к. увы число системных администраторов способных на это пока остается в меньшенстве.
Хм, перепроверил — а вы правы, черт побери! Видимо я последние тесты проводил или со странной сборкой IE или еще что, в любой случае склоняю шляпу, век живи — век учисью.
В общем же случае, если вы допускаете загрузку скриптов на ваш сервер — у вас уже проблемы, и рано или поздно кто-нибудь найдет способ это поэксплуатировать.
А с этим никто и не спорит, вопрос в другом — как максимально обезопасить свой аплоад разработчикам которые не знают на какой конкретной конфигурации будет работать их код (разработчики CMS/CMF, модулей и т.д.). Вот и приходится искать все возможные «странности» и подстраиваться под них, как в моем примере с nginx + файл только с расширением. Люди ведь извращенцы, некоторые работают и на связке IIS+PHP. С позиции разработки проекта все прозрачно — используем CDN, другой домен для статики и т.д.
Да, вы правы фактически имя у файла ".jpg" просто как иначе «обозвать» подобные файлы я увы не придумал, посему счел уместным употребить именно такое обозначение. Возможно, говорить «файлы без названия» было бы более корректным.
application/octet-stream безобидное? С чего бы вдруг? А как же mime-сниффинг в IE, включенный у абсолютного большинства его пользователей? Как раз по этой причине у меня обычно «default_type» и выставлен в force-download.
Достаточно часто встречаются ситуации когда люди пользуются какой либо функцией в фреймворке для описанных вами и совсем уж очевидных проверок, а сохраняют файл собственными силами и тогда оказывается, что при проверке делается trim (к примеру) а при сохранении вы его не делаете, что позволяет загрузить изображение с именем «file.jpg » и провернуть XSS для пользователей IE.
И совсем забыли об одной еще достаточно хитрой особенности nginx — нельзя допускать загрузки картинок без имени, только с расширением (например ".jpg"), т.к.:
Имхо, считаю статью из серии К.О.
Куда более интересней вопрос — правильная загрузка/отдача любых произвольных файлов, т.к. помимо изображений все чаще есть необходимость в загрузке тех же аудио/видео/офисный и иных файлов.
Увы не могу ничего сказать на тему стабильности работы подобной схемы на Lighttpd, проверял работоспособность только на nginx. Наши реалии таковы, что зачастую разработчики не умеют и не хотят правильно работать с файлами, обычно от незнания специфики веб-серверов на которых работают/могут работать их разработки, отсюда и получаем что допускаются к загрузке расширения с пробелами в конце (очень частый случай, когда при проверке в какой нить функции IsImage делается trim/rtrim а при сохранении нет) или же загрузка файлов только с расширением (например .jpg — смертельный файл для проекта в связке nginx + клиент на IE), а отдача всей статики кроме разрешенной с force-download их от этого убережет.
Это во-первых не даст выполнять pht файлы как php, о которых разработчики обычно не догадываются. Во-вторых это не позволит дергать php на файлах вида file.php.any
Да, ты прав, мой косяк. Думал везде поправил
А давайте продолжим список! Главное, что бы это могло привести к проблемам безопасности.
К примеру, с
proxy_set_header
я могу придумать кейс в котором это может стать проблемой (хоть пока и не встречал). А вот не сознательное переопределениеerror_page
не выглядит чем-то серьезным.Честно говоря, ничего не могу сказать по этому поводу. Я не большой сторонник панелей управления, поэтому опыта работы с ними крайне мало. Быть может, кто-то с большим опытом подскажет как правильно сделать интеграцию, какие существуют подводные камни и вот это все. А может и сделает это самостоятельно ;-)
— улучшить работу с переменными, добавить новых директив которые могут их предоставлять. Например, сейчас нет поддержки server_name :(
— продолжить улучшать работу с регулярками, что мы можно было делать более хитрые проверки. Кстати, я его обернул в самостоятельное приложение (бывает полезно, когда нужно быстренько проверить регулярочку): Regex Ninja
— разобраться с разными стьюпидами и написать хорошую документацию
Ну и, конечно же, написать новых проверок:)
Например, для Open Redirect'ов в реврайтах:
Экплуатация:
Именно поэтому я и начал с валидации регулярок в if. В будущем планирую добавить проверки для map (там будут примерно те же проблемы) и возможно добавить какую-то рекомендательную проверку по переписыванию if на map. Тут есть над чем подумать:)
Лучшим решением проблемы с OCSP Stapling'ом и WoSign — обновить nginx 1.7.11 (можно и ниже, просто не имеет смысла, ибо если уж обновлять до mainline то до текущего), там судя по коду все должно завестить без патчей и костылей (если не вылазить за 255 байт в закодированном OCSP-запросе) :-)
Вот сейчас для вашего домена у меня получается запрос к WoSign без доп. символов "/+=" (подлежащий кодированию при NGX_ESCAPE_URI_COMPONENT) алфавита base64, поэтому все и работает:
Поэтому за счет периодического везения у вас сложилось впечатление что все работает, т.к. nginx кеширует валидный ocsp-ответ на 1 час, а не валидный на 5 минут.
nginx в error.log'е ругался на не правильный тип ответа:
Не долго думая, решил посмотреть на трафик к OCSP Responder'у:
Отлично:-( Смотрим в RFC 6960:
С виду запрос от nginx полностью соответствует RFC. Немного поиграем:
Хм, любопытно. Похоже на расхождение логики nginx и OCSP Responder'а WoSign — первый считает OCSPRequest компонентом URI, поэтому закодировал не только основные разделители но и дополнительные (= в нашем случае), вторые же на это видимо не расчитывают.
Я нашел два варианта решения этой проблемы:
1. Скучный — исправить урл. Просто заменить ssl_stapling_responder на свой и с него уже запроксировать на ocsp6.wosign.com.
Добавляем сервер:
В настройках заменяем адрес OCSP Responder'а:
Делаем тестовый запрос и смотрим на трафик:
Выглядит работоспособным. Тест так же говорит что OCSP Stapling работает
2. Интересный:) Будучи программистом — мне больше нравится исправлять код, нежели раставлять костыли в конфигах. Поэтому я сделал простой патч gist.github.com/buglloc/6f14a16ab702478d23e3 который добавляет булевую опцию ssl_stapling_force_post по которой nginx начинает делать POST запрос вместо GET.
После этого идем в настройки и добавляем:
Перезапускаем nginx и опять смотрим на трафик:
Выглядит корректным, проверяем:
Надеюсь кому-то поможет. Я старался обойтись минимальными правками и вроде достаточно внимательно поправил код. Конечно, скорее более правильно было бы сделать как современные браузеры (которые славятся своей «приспосабливаемостью»;) — они зачастую сначала шлют GET и в случае провала пытаются сделать POST запрос. Но патч получился бы менее легким и читабельным, поэтому я не стал этого делать.
Подправил, спасибо.
А с этим никто и не спорит, вопрос в другом — как максимально обезопасить свой аплоад разработчикам которые не знают на какой конкретной конфигурации будет работать их код (разработчики CMS/CMF, модулей и т.д.). Вот и приходится искать все возможные «странности» и подстраиваться под них, как в моем примере с nginx + файл только с расширением. Люди ведь извращенцы, некоторые работают и на связке IIS+PHP. С позиции разработки проекта все прозрачно — используем CDN, другой домен для статики и т.д.
application/octet-stream безобидное? С чего бы вдруг? А как же mime-сниффинг в IE, включенный у абсолютного большинства его пользователей? Как раз по этой причине у меня обычно «default_type» и выставлен в force-download.
И совсем забыли об одной еще достаточно хитрой особенности nginx — нельзя допускать загрузки картинок без имени, только с расширением (например ".jpg"), т.к.:
Имхо, считаю статью из серии К.О.
Куда более интересней вопрос — правильная загрузка/отдача любых произвольных файлов, т.к. помимо изображений все чаще есть необходимость в загрузке тех же аудио/видео/офисный и иных файлов.
Увы не могу ничего сказать на тему стабильности работы подобной схемы на Lighttpd, проверял работоспособность только на nginx. Наши реалии таковы, что зачастую разработчики не умеют и не хотят правильно работать с файлами, обычно от незнания специфики веб-серверов на которых работают/могут работать их разработки, отсюда и получаем что допускаются к загрузке расширения с пробелами в конце (очень частый случай, когда при проверке в какой нить функции IsImage делается trim/rtrim а при сохранении нет) или же загрузка файлов только с расширением (например .jpg — смертельный файл для проекта в связке nginx + клиент на IE), а отдача всей статики кроме разрешенной с force-download их от этого убережет.
Естественно должно быть так:
и не забыть вычистить php из типов (зависит от ОСи):
Это во-первых не даст выполнять pht файлы как php, о которых разработчики обычно не догадываются. Во-вторых это не позволит дергать php на файлах вида file.php.any