• Партнер

  • В ModSecurity — известном WAF для Apache, IIS и nginx — нашли критическую уязвимость, которая приводит к отказу в обслуживании. Причем завершает работу не только сама библиотека, но и приложения, которые ее вызывают. Давай разберемся, в чем ошиблись разработчики ModSecurity и как эксплуатировать эту лазейку при пентестах.

    У ModSecurity есть свой скриптовый язык, основанный на событиях. Он обеспечивает защиту от множества видов атак на веб-приложения и позволяет мониторить HTTP-трафик, вести логи и анализировать запросы в реальном времени. Это делает ModSecurity очень гибким инструментом для поиска потенциально небезопасных данных и реакции на них.

    Баг обнаружили Эрвин Хегедюш (Ervin Hegedüs) и Андреа Менин (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 на борту и установки всех необходимых пакетов.

    docker run --rm -ti --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --name=nginxdos --hostname=nginxdos -p80:80 debian /bin/bash
    apt update && apt install nano wget git procps gdb automake bison build-essential g++ gcc libbison-dev libcurl4-openssl-dev libfl-dev libgeoip-dev liblmdb-dev libpcre3-dev libtool libxml2-dev libyajl-dev make pkg-config zlib1g-dev
    

    Теперь скачиваем библиотеку ModSecurity последней уязвимой версии 3.0.3.

    cd /root
    git clone --depth 1 -b v3.0.3 --single-branch https://github.com/SpiderLabs/ModSecurity
    

    Далее подгрузим все необходимые модули.

    cd ModSecurity/
    git submodule init
    git submodule update
    

    А потом скомпилируем ее и установим.

    ./build.sh && ./configure && make && make install
    
    Установка уязвимой версии библиотеки ModSecurity
    Установка уязвимой версии библиотеки ModSecurity

    Пришел черед коннектора для nginx.

    cd /root && git clone --depth 1 https://github.com/SpiderLabs/ModSecurity-nginx.git
    

    Ну и сам веб-сервер, разумеется.

    wget -q https://nginx.org/download/nginx-1.16.1.tar.gz
    tar -zxf nginx-1.16.1.tar.gz
    cd nginx-1.16.1
    

    Теперь сконфигурируем его для использования библиотеки ModSecurity.

    ./configure --add-module=/root/ModSecurity-nginx
    

    Дальше дело за компиляцией и установкой.

    make && make install
    
    Компиляция и установка nginx с поддержкой ModSecurity
    Компиляция и установка nginx с поддержкой ModSecurity

    Для более наглядных тестов я также установлю и запущу PHP-FPM.

    apt install -y php-fpm
    service php7.3-fpm start
    

    По дефолту nginx ставится в директорию /usr/local/nginx/. Перейдем туда, чтобы немного подправить конфиги под наши реалии. Сначала включим поддержку PHP. У меня PHP-FPM работает через сокет, поэтому пропишем путь до него в раздел server файла nginx.conf.

    location ~ \.php$ {
        root           html;
        fastcgi_pass   unix:/run/php/php7.3-fpm.sock;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include        fastcgi_params;
    }
    

    Теперь переходим к настройке ModSecurity. Добавим необходимую директиву во все тот же nginx.conf, только на этот раз в раздел http.

    modsecurity on;
    

    Так как у нас будет несколько конфигурационных файлов, сделаем один общий modsec_includes.conf, в который будем добавлять список необходимых конфигов. Подгрузим его при помощи modsecurity_rules_file.

    modsecurity_rules_file /usr/local/nginx/conf/modsec_includes.conf;
    

    Далее нужно скопировать дефолтный конфиг (modsecurity.conf-recommended) из дистрибутива ModSecurity.

    cp /root/ModSecurity/modsecurity.conf-recommended /usr/local/nginx/conf/modsecurity.conf
    cp /root/ModSecurity/unicode.mapping /usr/local/nginx/conf/
    

    Переключим библиотеку из пассивного режима в режим блокировки.

    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
    

    И наконец, два вспомогательных конфига — для инициализации правил и блокировки запроса на основе системы скоринга.

    wget https://raw.githubusercontent.com/SpiderLabs/owasp-modsecurity-crs/v3.3/dev/rules/REQUEST-901-INITIALIZATION.conf
    wget https://raw.githubusercontent.com/SpiderLabs/owasp-modsecurity-crs/v3.3/dev/rules/REQUEST-949-BLOCKING-EVALUATION.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
    

    Обрати внимание на последовательность загрузки, она важна.

    На этом этап подготовки стенда закончен, осталось его проверить. Запустим веб-сервер.

    /usr/local/nginx/sbin/nginx -g "daemon off;master_process off;error_log /dev/stdout debug;"
    

    Я добавил несколько параметров, чтобы отключить запуск nginx как демона и вывести логи в консоль. Если теперь отправить потенциально опасный запрос на сервер — http://nginxdos.vh/?xss=<script>alert()</script>, то ModSecurity заблокирует запрос и вернет 403.

    Готовый стенд. ModSecurity заблокировала потенциально опасный запрос к серверу
    Готовый стенд. ModSecurity заблокировала потенциально опасный запрос к серверу

    Вот и готовый стенд. Переходим к препарированию уязвимости.

     

    Детали

    Начнем с того, что посмотрим на патч, который исправляет уязвимость. Изменения коснулись файла 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
    Отладка сервера 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 на пару имя-значение
    Разбиваем содержимое cookie на пару имя-значение

    Пока все идет хорошо. Если обратиться к разделу 5.2 спецификации RFC 6265 о механизмах хранения состояния в HTTP, то там под пунктом 2 увидим, что если в паре имя-значение отсутствует символ = (%x3D), то его нужно игнорировать.

    Продолжение доступно только участникам

    Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте

    Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее

    Вариант 2. Открой один материал

    Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.


    Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии