ADG Оставить заявку
Блог Информационная безопасность 4 мин чтения

Log4Shell playbook: три волны за неделю и восемь клиентов параллельно

CVE-2021-45046 показал, что 2.15.0 - не финиш. Рассказываем, как выстроили трёхрубежный playbook и вели 8 клиентов одновременно через волны патчинга.

Контекст момента

CVE-2021-45046 (Log4j 2.15.0 неполный фикс) и CVE-2021-45105 - три волны патчинга за неделю после Log4Shell

9 декабря мы разобрались с первой волной Log4Shell - CVE-2021-44228, CVSS 10.0, экстренный митигейшн по всем клиентам. Казалось бы, ситуация взята под контроль. Но к 13 декабря выяснилось, что Log4j 2.15.0 - тот самый «фикс», на который мы обновлялись - закрыл уязвимость не полностью. CVE-2021-45046. А затем, не давая выдохнуть, прилетел CVE-2021-45105 - DoS через бесконечную рекурсию при логировании. Три CVE за неделю, каждый требует своего маршрута в changelog и своей строчки в трекере по каждому клиенту. Вот что из этого вышло.

Почему три волны - это не просто «патчи»

Первая реакция на новость о 2.15.0 была приблизительно такая: ладно, обновимся ещё раз. Но быстро стало понятно, что дело не в количестве обновлений. Каждая новая CVE меняла условия для тех, кто не успел завершить предыдущий цикл. У части клиентов вендорские компоненты всё ещё ждали патча от производителя. Mitigation через -Dlog4j2.formatMsgNoLookups=true, который мы применяли как временную меру для CVE-2021-44228, оказался недостаточным для CVE-2021-45046 в non-default конфигурациях. Нужен был другой подход, и не один.

Мы оформили это как трёхрубежный playbook с явными приоритетами:

  • Первый рубеж - WAF. Самое быстрое, что можно сделать без касания приложения. Развернули правила блокировки JNDI-паттернов во входящих данных: ${jndi:, ${${::-j}${::-n}${::-d}${::-i}:, обфусцированные варианты с lower:, upper:, :- внутри. Важный момент: WAF здесь - не «закрыть и забыть», а выиграть время для второго рубежа. Обходы правил публикуются в реальном времени, гонка не в нашу пользу.
  • Второй рубеж - отключение JNDI lookup на уровне JVM. log4j2.formatMsgNoLookups - это один метод. Но для CVE-2021-45046 нужен log4j2.noFormatMsgLookup плюс отдельная проверка конфигурации PatternLayout. Там, где был контроль над запуском JVM, добавляли -Dlog4j2.formatMsgNoLookups=true -Dlog4j2.noFormatMsgLookup=true вместе. Там, где не было - переменные окружения и пересмотр того, кто вообще управляет JVM-конфигурацией в данной системе.
  • Третий рубеж - обновление до 2.17.0. Не до 2.15.0 (неполный фикс CVE-2021-44228), не до 2.16.0 (закрывает 45046, но падает от 45105 при рекурсивном lookup в конфиге), а именно до 2.17.0. Это и есть финальная точка, по состоянию на сегодня.

Восемь клиентов одновременно

Ситуация усложнялась тем, что у каждого клиента своё состояние: кто-то успел обновиться до 2.16.0 после CVE-2021-45046, и теперь нужно ещё раз двигаться до 2.17.0. У кого-то вендорский компонент так и не получил патча - там первый и второй рубеж остаются активными без возможности перейти к третьему. У одного клиента Jenkins обновился быстро, зато Elasticsearch 6.x, который тоже тянул Log4j, формально находится за пределами поддержки, и команда разработки просто не собирала его в последние полгода.

Для координации мы сделали общую таблицу состояний - ничего экзотического, просто реестр с колонками: компонент, текущая версия Log4j, CVE-2021-44228 статус, CVE-2021-45046 статус, CVE-2021-45105 статус, ответственный, дедлайн. Обновляется после каждого звонка с командой клиента. Это звучит скучно, но именно это не даёт потерять в потоке событий компонент, который «уже закрыт» по одной CVE и открыт по следующей.

Одно наблюдение по координации: звонки с командами клиентов в период активного инцидента имеет смысл делать короткими и с конкретным вопросом. «Расскажите статус» - плохой вопрос. «Elasticsearch на prod-кластере - какая версия Log4j и кто запускает JVM-аргументы» - хороший. Когда всё горит, люди не успевают подготовить «статус», но на конкретный вопрос отвечают сразу.

Что не сработало

WAF-правила на один из клиентских прокси-серверов добавили с задержкой из-за процедур согласования изменений. Это две лишние ночи с открытым первым рубежом. На будущее договорились, что emergency security change идёт по отдельному треку без ожидания стандартного approval-цикла.

Mitigation через formatMsgNoLookups на одном из компонентов пришлось переделывать: оказалось, что флаг передаётся через startup-скрипт, который переопределяет переменные окружения. Применили флаг - он молча не применился, потому что скрипт его сбросил. Проверка того, что флаг действительно активен - отдельный шаг, который нельзя пропускать.

Где мы сейчас

По большинству клиентов первый и второй рубежи активны, обновление до 2.17.0 идёт по расписанию - там, где возможно без вендора. Несколько компонентов ждут официального патча от производителей оборудования и ПО. По ним WAF-правила и JNDI-ограничения остаются включёнными как единственная защита.

История с Log4Shell показала, что managed-сопровождение в момент такого инцидента - это не «быстро нажать кнопку обновить». Это координация между несколькими командами, несколькими состояниями одного и того же компонента у разных клиентов и непрерывно меняющейся картиной по CVE. Три рубежа хороши тем, что они применяются независимо и дают промежуточный результат даже когда третий рубеж недоступен. Это и есть практическая ценность layered defense - не как принцип из учебника, а как рабочий инструмент в конкретный понедельник декабря.

Контакт

Нужна такая же инженерная работа?

Опишите задачу и контекст. Ответим в течение рабочего дня, при необходимости подпишем NDA.