
Доказательство концепции, демонстрирующее обход фильтра десериализации в Apache MINA, приводящий к удалённому выполнению кода, с подробным анализом первопричины, эксплойтами PoC и рекомендациями по устранению уязвимости.
CVSS 3.1: 9.8 КРИТИЧЕСКИЙ AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CWE: CWE-502 Десериализация недоверенных данных
Автор отчёта: Venkatraman Kumar, Securin
Консультация: Список рассылки Apache
Версии Apache MINA 2.1.0–2.1.11 и 2.2.0–2.2.6 содержат обход фильтра десериализации в AbstractIoBuffer.resolveClass(). Белый список acceptMatchers — предназначенный для ограничения того, какие классы Java могут быть десериализованы — полностью пропускается, когда ObjectStreamClass.forClass() возвращает null.
Злоумышленник, имеющий сетевой доступ к конечной точке MINA, использующей ObjectSerializationCodecFactory, может сформировать полезную нагрузку протокола, которая обходит фильтр классов, что обеспечивает полное удалённое выполнение кода через стандартные цепочки гаджетов десериализации Java (например, Commons Collections).
Это неполное исправление для CVE-2026-41635. Оригинальный патч был применён к ветке 2.0.x, но так и не был перенесён в 2.1.x или 2.2.x из-за ошибки при слиянии.
| Ветка | Уязвимые | Исправленные |
|---|---|---|
| 2.1.x | 2.1.0 – 2.1.11 | 2.1.12 |
| 2.2.x | 2.2.0 – 2.2.6 | 2.2.7 |
Уязвимость находится в AbstractIoBuffer.resolveClass(), который обрабатывает разрешение классов во время десериализации объектов Java.
MINA использует собственный протокол сериализации с двумя типами дескрипторов классов:
В уязвимом коде фильтр acceptMatchers проверяется только в ветке типа 1 (когда forClass() возвращает не-null). Ветка типа 0 вызывает Class.forName() напрямую, полностью обходя фильтр:
// AbstractIoBuffer.java — УЯЗВИМЫЙ (2.2.6)
protected Class<?> resolveClass(ObjectStreamClass desc) {
Class<?> clazz = desc.forClass();
if (clazz == null) {
// ОШИБКА: проверка acceptMatchers отсутствует — фильтр полностью обойдён
return Class.forName(name, false, classLoader);
} else {
// Фильтр применяется только здесь
for (ClassNameMatcher matcher : acceptMatchers) { ... }
}
}
Исправление в 2.2.7 перемещает проверку фильтра перед ветвлением:
// AbstractIoBuffer.java — ИСПРАВЛЕННЫЙ (2.2.7)
protected Class<?> resolveClass(ObjectStreamClass desc) {
String className = desc.getName();
// Фильтр применяется СНАЧАЛА, независимо от результата forClass()
if (!acceptMatchers.stream().anyMatch(m -> m.matches(className))) {
throw new ClassNotFoundException("Class not in accept list " + className);
}
Class<?> clazz = desc.forClass();
// ... далее безопасное разрешение
}
Злоумышленник Уязвимый сервер MINA
| |
| 1. Формирует полезную нагрузку MINA с |
| дескрипторами типа 0 для классов |
| цепочки гаджетов |
| |
| 2. Отправляет на конечную точку, |
| использующую |
| ObjectSerializationCodecFactory -------->|
| |
| 3. readClassDescriptor() читает тип 0|
| → делегирует в super (стандартная Java)
| |
| 4. resolveClass() видит forClass()==null
| → Class.forName() БЕЗ фильтра |
| |
| 5. Цепочка гаджетов полностью |
| десериализована |
| → readObject() запускает цепочку |
| → Runtime.exec() срабатывает |
| |
| RCE ДОСТИГНУТ |
IoBuffer.getObject() или ObjectSerializationCodecFactoryaccept() (приложения без фильтра уже были эксплуатируемы через CVE-2026-41635)Злоумышленник контролирует сериализованный поток байтов. Используя дескрипторы классов типа 0 (вместо типа 1) для сериализуемых классов цепочек гаджетов, каждый класс в графе десериализации обходит фильтр acceptMatchers, независимо от конфигурации белого списка приложения.
Три PoC демонстрируют возрастающее воздействие:
| PoC | Что доказывает |
|---|---|
FilterBypassPoC.java | Обход фильтра для примитивов, несериализуемых классов, массивов |
CraftedBypassPoC.java | Сформированные злоумышленником полезные нагрузки типа 0 обходят фильтр для ЛЮБОГО сериализуемого класса |
RcePoC.java | Полный RCE через цепочку гаджетов CC6 с обходом фильтра |
Классы, отсутствующие в списке accept, десериализуются без ограничений:

Злоумышленник формирует полезные нагрузки протокола MINA с дескрипторами типа 0 для загрузки любого класса в обход списка accept, содержащего только String:

Вариант цепочки гаджетов CC6 (HashSet → TiedMapEntry → LazyMap → ChainedTransformer → Runtime.exec()) обеспечивает выполнение команд через обход фильтра:

Те же тесты блокируются на исправленной версии:

Цепочка гаджетов отклоняется фильтром:

Самый быстрый способ тестирования — без JDK или Maven:
# Клонировать этот репозиторий
git clone https://github.com/dinosn/CVE-2026-42779.git
cd CVE-2026-42779
# Собрать и запустить все PoC
docker build -t cve-2026-42779 .
docker run --rm cve-2026-42779
# Запустить отдельные PoC
docker run --rm cve-2026-42779 bypass # Только обход фильтра
docker run --rm cve-2026-42779 crafted # Обход сформированной полезной нагрузкой
docker run --rm cve-2026-42779 rce # Полный RCE
# Войти в оболочку для изучения
docker run --rm -it cve-2026-42779 shell
Образ включает уязвимый JAR MINA 2.2.6, Commons Collections 3.2.2 и все три предварительно скомпилированных PoC. Всё работает автономно внутри контейнера.