В ModSecurity — известном WAF для Apache, IIS и nginx — нашли критическую уязвимость, которая приводит к отказу в обслуживании. Причем завершает работу не только сама библиотека, но и приложения, которые ее вызывают. Давай разберемся, в чем ошиблись разработчики ModSecurity и как эксплуатировать эту лазейку при пентестах.
У ModSecurity есть свой скриптовый язык, основанный на событиях. Он обеспечивает защиту от множества видов атак на веб-приложения и позволяет мониторить HTTP-трафик, вести логи и анализировать запросы в реальном времени. Это делает ModSecurity очень гибким инструментом для обнаружения потенциально небезопасных данных и реакции на них.
Баг обнаружили Эрвин Хегедюш (Ervin Hegedus) и Андреа Менин (Andrea Menin), разработчики OWASP Core Rule Set, когда изучали работу парсера запросов, в частности обработку хидера cookie.
Баг позволяет злоумышленнику отправить специально сформированный запрос, который будет завершать работу родительского процесса. Отправка большого количества таких запросов может привести к тому, что веб-сервер станет отвечать гораздо медленнее или вообще перестанет реагировать на запросы пользователей (отказ в обслуживании).
INFO
Уязвимость получила идентификатор CVE-2019-19886 и затрагивает все версии Trustwave ModSecurity ветки 3.х, начиная с 3.0.0 и заканчивая 3.0.3 включительно.
Стенд
Сначала нужно поднять тестовое окружение. Здесь есть два варианта.
Если не хочешь возиться с отладкой и копанием в сорцах, то можешь просто запустить докер-контейнер с уязвимой версией ModSecurity и потестить эксплоит.
docker run --rm -p 80:80 -ti --rm owasp/modsecurity:3.0.3-nginx
Второй вариант — скомпилить все из сорцов с возможностью дебага.
Начнем с запуска контейнера с Debian на борту и установки всех необходимых пакетов.
Теперь переходим к настройке ModSecurity. Добавим необходимую директиву во все тот же nginx.conf, только РЅР° этот раз РІ раздел http.
modsecurity on;
Так как у нас будет несколько конфигурационных файлов, сделаем один общий modsec_includes.conf, РІ который будем добавлять СЃРїРёСЃРѕРє необходимых конфигов. РџРѕРґРіСЂСѓР·РёРј его РїСЂРё помощи modsecurity_rules_file.
Переключим библиотеку из пассивного режима в режим блокировки.
sed 's/SecRuleEngine DetectionOnly/SecRuleEngine On/' -i modsecurity.conf
Пришел черед фильтров. Я рекомендую использовать набор правил OWASP Core Rule Set. Для наших тестов достаточно будет только нескольких файлов. В первую очередь загрузим основной конфиг.
mkdir /usr/local/nginx/conf/modsec && cd $_
wget https://raw.githubusercontent.com/SpiderLabs/owasp-modsecurity-crs/v3.3/dev/crs-setup.conf.example -O crs-setup.conf
Затем мой выбор пал на файл с правилами для предотвращения XSS-уязвимостей.
mkdir /usr/local/nginx/conf/modsec/rules && cd $_
wget https://raw.githubusercontent.com/SpiderLabs/owasp-modsecurity-crs/v3.3/dev/rules/REQUEST-941-APPLICATION-ATTACK-XSS.conf
И наконец, два вспомогательных конфига — для инициализации правил и блокировки запроса на основе системы скоринга.
Теперь нужно использовать все эти правила в нашей конфигурации nginx. Для этого я и создавал файл /usr/local/nginx/conf/modsec_includes.conf.
include modsecurity.conf include modsec/crs-setup.conf include modsec/rules/REQUEST-901-INITIALIZATION.conf include modsec/rules/REQUEST-941-APPLICATION-ATTACK-XSS.conf include modsec/rules/REQUEST-949-BLOCKING-EVALUATION.conf
Обрати внимание на последовательность загрузки, она важна.
На этом этап подготовки стенда закончен, осталось его проверить. Запустим веб-сервер.
Я добавил несколько параметров, чтобы отключить запуск nginx как демона и вывести логи в консоль. Если теперь отправить потенциально опасный запрос на сервер — http://nginxdos.vh/?xss=<script>alert()</script>, то ModSecurity заблокирует запрос Рё вернет 403.
Начнем СЃ того, что посмотрим РЅР° патч, который исправляет уязвимость. Р?зменения коснулись файла transaction.cc в разделе парсинга куков. Запустим веб-сервер через отладчик и поставим бряк на строку 558, чтобы потрейсить процесс обработки.
gdb --arg /usr/local/nginx/sbin/nginx -g "daemon off;master_process off;error_log /dev/stdout debug;" b transaction.cc:558 r
Отладка сервера nginx с библиотекой ModSecurity. Ставим брейк-пойнт на парсере заголовка cookie
Отправляем запрос с куками и попадаем в точку останова.
curl -v -H "Cookie: hello=world" nginxdos.vh
Сработал брейк-пойнт в процессе парсинга куков
Как ты, скорее всего, знаешь, в протоколе HTTP строка cookie представляет СЃРѕР±РѕР№ последовательность пар РёРјСЏ=значение, разделенных символом ;. Поэтому изначально РІСЃСЏ строка разбивается РЅР° части, РіРґРµ разделителем служит ;. Результат записывается в вектор (std::vector).
modsec/v3.0.3/src/transaction.cc
557: if (keyl == "cookie") { 558: size_t localOffset = m_variableOffset; 559: std::vector<std::string> cookies = utils::string::ssplit(value, ';');
Вектор в C++ — это замена стандартному динамическому массиву, который может управлять выделенной для него памятью. С его помощью можно создавать массивы, длина которых задается во время выполнения, без использования операторов new Рё delete. Все элементы вектора должны принадлежать одному типу. В дополнение к функциям прямого доступа элементы вектора можно получить посредством итераторов. Что и происходит дальше по коду.
modsec/v3.0.3/src/transaction.cc
560: for (const std::string &c : cookies) {
Перебираем все переданные в запросе куки
Так как я передал одну куку, то и элемент всего один.
Теперь он разбивается по разделителю =. Таким образом получаем РёРјСЏ Рё значение cookie.
561: std::vector<std::string> s = utils::string::split(c, 562: '=');
Дальше проверяется размер полученного вектора, и, если он больше единицы, выполнение продолжается.
modsec/v3.0.3/src/transaction.cc
563: if (s.size() > 1) {
Разбиваем содержимое cookie на пару имя-значение
Пока все идет хорошо. Если обратиться к разделу 5.2 спецификации RFC 6265 о механизмах хранения состояния в HTTP, то там под пунктом 2 увидим, что если в паре имя-значение отсутствует символ = (%x3D), то его нужно игнорировать.