Пару недель назад команда Vulners опубликовала сравнение нескольких популярных WAF. Поймав себя на мысли - "а как оценивать качество его работы?", я решил разобрать подробнее тему security-тестов и критериев оценки Web Application Firewall. Статья пригодится, в первую очередь, тем, кому интересна тема веб-безопасности, ровно как и счастливым обладателям WAF.
Критерии оценки
Сравнивая различные решения, мы привыкли ориентироваться на показатели скорости и стабильности работы, удобства настройки, управления, обновления и масштабирования. В контексте WAF к этому списку нужно добавить и точность выявления атак - количество пропусков (False Negative) и ложных срабатываний (False Positive).
Зоопарк из UTF-8
Чтобы понять, сколько тонкостей существует при анализе запросов, рассмотрим в качестве примера особенность работы с UTF-8. Если в поисковой строке сайта мы наберем что-то в английской раскладке, то, скорее всего, будет использоваться набор Basic Latin.
При этом запрос, который будет передаваться в веб-приложение, выглядит следующим образом: GET /?s=test
Теперь попробуем использовать набор символов из Halfwidth and Fullwidth Forms (ищем тот же test, только с другим набором символов).
Выглядит так, будто мы использовали пробел между символами, но на самом деле - нет.
Запрос (в URLencode): GET /?s=%EF%BD%94%EF%BD%85%EF%BD%93%EF%BD%94
При этом оба запроса (с разными наборами символов) приведут к выводу одного и того же результата - объекта, содержащего вхождение "test".
Сперва мы подумали, что само веб-приложение (проверяли на WordPress, но отработает и на других) производит нормализацию данных до Basic Latin, но в итоге выяснили, что данные передаются без нормализации напрямую в БД. Есть ограничения, связанные с использованием Halfwidth and Fullwidth Forms - его нельзя применять при написании оператора SELECT
, но можно использовать, к примеру, в конструкции LIKE '%Текст в Halfwidth and Fullwidth Forms%'
. Этот набор символов может использоваться не только при составлении SQL-запросов. Есть примеры его применения при поиске XSS и т.д.
Данная особенность позволяет выполнять обход WAF, если он не нормализует символы до Basic Latin. Существует множество техник обхода, и было бы здорово создать инструмент, позволяющий разом их все проверить.
WAF Bypass: инструментарий
На github можно найти множество решений, позволяющих тестировать WAF, есть даже специальные аккаунты, производящие сбор такого инструментария, например waflib.
Помимо основного Unit-теста, который используем для тестирования Nemesida WAF перед каждым релизом (тест на каждую версию ОС занимает примерно 30 часов), мы разработали собственный waf-bypass, позволяющий оценить точность выявления атак. Но всегда интересно и полезно проверить работу продукта с использованием сторонних решений, поэтому в качестве второго (а в рамках этой статьи - основного) инструмента я выбрал Go Test WAF, размещенный в git-репозитории пользователя wallarm.
Для использования waf-bypass, написанного на Python3, необходимо клонировать проект, установить зависимости и запустить скрипт:
git clone https://github.com/nemesida-waf/waf_bypass.git /opt/waf-bypass/
python3 -m pip install -r /opt/waf-bypass/requirements.txt
python3 /opt/waf-bypass/main.py --host='example.com'
Если следовать документации, Go Test WAF можно запускать из докер-контейнера, что я и сделал:
docker build . --force-rm -t gotestwaf
docker run gotestwaf --url='http://example.com'
Если проводить сравнение инструментов исходя из их использования, то Go Test WAF работает быстрее (за счет многопоточности) и содержит небольшой набор для проверки False Positive. Учтем это в следующих релизах waf-bypass.
Вижу цель, иду к ней!
Тестировать буду 2 версии: Nemesida WAF (полноценную с машинным обучением) и Nemesida WAF Free (бесплатную, но только с сигнатурным анализом).
В обзоре Vulners в финальной таблице максимальным результатом WAF, полученным путем тестирования Go Test WAF, являлось значение 83.06%, при этом ModSecurity набрал 49.82%. Интересно посмотреть, сколько сможет набрать Nemesida WAF и Nemesida WAF Free.
Nemesida WAF Free bypass
Напомню, анализ в Nemesida WAF Free производится только сигнатурным методом, при этом эта версия имеет качественно написанную и автоматически обновляемую базу сигнатур (ознакомиться с которой можно по ссылке), а также Личный кабинет (также устанавливается локально) - веб-интерфейс для визуализации событий. Nemesida WAF Free представлена в виде динамического модуля, который можно подключать как к новому экземпляру Nginx, так и к уже установленному (или скомпилированному с собственными флагами). Обновляется Nemesida WAF Free из репозитория и доступен для Debian, Ubuntu и CentOS, "быстрый старт" займет 5-7 минут для опытных пользователей.
Запускаем Go Test WAF, смотрим результаты.
45.47% и 0 ложных срабатываний - довольно неплохо, учитывая, что часть тестов содержит пейлоады в Base64, которые не декодируются в Nemesida WAF Free. Теперь ловким движением руки и не меняя настроек Nemesida WAF Free, проверим его с помощью waf-bypass:
325 пропусков и 1054 заблокированных пейлоадов. Пытаемся запомнить результаты и двигаемся дальше.
Nemesida WAF bypass
Помимо функционала нормализации данных, Nemesida WAF позволяет производить декодирование запросов в различных кодировках, в том числе и в Base64, поэтому было особенно интересно, какой скор он сможет набрать. Начнем с waf-bypass, запускаем, смотрим результаты:
4 пропуска, остальные запросы заблокированы. Здорово, но все-таки waf-bypass - наша разработка, поэтому очень интересно, какие результаты будут получены при тестировании с использованием Go Test WAF. Запускаем Go Test WAF, смотрим результаты:
Проверка заняла немного больше времени, но и показатели в 2 раза лучше - 94.45%, и на 11.39% выше, чем наилучший результат в обзоре Vulners. Почти отсутствуют пропуски, но из 8 False Positive заблокировано 5, а хотелось, чтобы 0, поэтому будем разбираться. Сперва посмотрим содержимое всех 8 FP-запросов:
- union was a great select
- 'h2<h1'
- D'or 1st parfume
- "1) a-b=c"
- john+or@var.es
- DEAR FINN,--I think it would do; copy should reach us second post
- The Senora found herself a heroine; more than that, she became aware time he came.
Из них ошибочно заблокированные:
- f971e9ace6=union was a great select
- 24d85a0990=D'or 1st parfume
- 48105a7f77=john+or@var.es
- 18196a6191=DEAR FINN,--I think it would do; copy should reach us second post
- e7e5421304=The Senora found herself a heroine; more than that, she became aware
Чтобы понять, почему запросы были заблокированы, нужно понимать, как работает модуль машинного обучения в Nemesida WAF - построение моделей происходит за счет использования 2-х наборов обучающих выборок - базы атак и базы условно чистого трафика. Чем меньше реальных запросов попало в обучающую выборку - тем больше будет False Positive. В данном случае в обучающей выборке не попадались такие или схожие запросы, кроме этого, они содержат признаки атаки (например, --
, или union ... select
, или 'or
), поэтому были заблокированы. Используя модуль дообучения Nemesida WAF Signtest, производим экспорт False Positive в один клик, после чего они войдут в обучающую выборку и не будут учитываться - как сами, так и другие, схожие с ними запросы.
Запускаем тест заново:
Несмотря на то, что содержимое запросов имеет динамический контент 24d85a0990
в 24d85a0990=D'or 1st parfume
, ложные срабатывания отсутствуют, при этом количество пропусков не изменилось. Скоринг стал меньше, а должен был увеличиться - вероятно, ошибка в самом Go Test WAF.
Чтобы разобраться в пропусках, я настроил журналирование Nginx таким образом, чтобы фиксировать содержимое запросов, не получивших код ответа 403:
Расширенное журналирование Nginx
Если вы хотите проверить, какие запросы не были заблокированы - активируйте расширенное журналирование в Nginx:
1. В секцию http {}
добавив:
logformat extended '$msec $requesttime $timeiso8601 $timelocal $requestid $status $remoteaddr $scheme $host $request $httphost $httpcookie $httpuseragent $contentlength $contenttype $httporigin $requestbody\n';
и
map $status $loggable { 403 0; default 1; }
2. В секцию server {}
файла виртуального хоста (например, /etc/nginx/conf.d/1.conf) добавив:
access_log /var/log/nginx/extended_access.log extended if=$loggable;
После перезапуска Nginx в /var/log/nginx/extended_access.log будут попадать все незаблокированные запросы (не получившие код ответа 403).
Фактически пропусков было всего 3, и об их критичности судите сами:
GET /AEAEAFAEAEAFetcAFpasswd
GET / QUIT /
GET /foo_bar=foo bar=foo[barfoo[bar
Визуализация атак: графики, поиск, отчеты
Вне зависимости от того, используется ли Nemesida WAF или Nemesida WAF Free, к любому из них можно подключить Личный кабинет - веб-интерфейс, позволяющий визуализировать работу модулей:
В Личном кабинете есть и другие разделы - информация по атакам методом перебора и SMS-флуде, статистика работы сканера уязвимостей и т.д. Также можно выгрузить общий отчет в формате PDF или CSV. Личный кабинет написан на Django и устанавливается локально, так же, как и другие модули.
Демонстрационный стенд:
demo.lk.nemesida-security.com (demo@pentestit.ru / pentestit)
И пока на версусе решают, кто из них круче...
Как я писал выше, есть несколько показателей оценки качества работы ПО, для WAF одним из основных критериев является количество ложных срабатываний и пропусков, и было очень увлекательно и полезно выполнять проверки с использованием сторонних байпасеров, за что отдельная благодарность разработчикам Go Test WAF.
Кроме этого, такие тесты позволяют наглядно понять разницу между сигнатурным анализом и машинным обучением, необходимость использовать различные механизмы нормализации и декодирования. Но если по какой-то причине использовать коммерческий продукт нет возможности, всегда можно воспользоваться ModSecurity, NAXSI или Nemesida WAF Free с красивым и удобным Личным кабинетом. Nemesida WAF Free доступен в виде установочных дистрибутивов для Debian/Ubuntu/CentOS, в виде Virtual Appliance для KVM/VirtualBox/VMWare и образа для Docker-контейнера. Больше информации можно найти здесь.
Спасибо, что дочитали, если появятся какие-то мысли - можно обсудить в комментариях.