CVE-2026-34486: как заплатка открыла новую дыру в Apache Tomcat
Представьте: вы получили уведомление об уязвимости в Apache Tomcat, быстро обновились до версии 9.0.116, закрыли задачу в трекере и выдохнули. А потом через месяц выяснилось, что именно этот патч и создал новую дыру — на этот раз даже более опасную, чем исходная.
CVE-2026-34486 — классическая регрессия: разработчики исправляли CVE-2026-29146, padding oracle-атаку на Tomcat Tribes, но допустили логическую ошибку, которая открыла путь для unauthenticated RCE через десериализацию. Apache оценил как Important, NVD присвоил CVSS 7.5 — и это тот случай, когда цифра не передаёт реальной опасности: предыдущая уязвимость с тем же компонентом требовала часов работы, эта эксплуатируется одним пакетом. Затронуты версии 9.0.116, 10.1.53 и 11.0.20. PoC опубликован исследователем Bartlomiej Dmitruk из striga.ai.
Патч вышел 9 апреля 2026 года — коммит 776e12b3 в официальном репозитории. Если вы обновлялись конкретно ради закрытия CVE-2026-29146 и встали на 9.0.116 — вы до сих пор уязвимы. Нужен 9.0.117.
ЧТО ТАКОЕ APACHE TOMCAT TRIBES
Apache Tribes — это внутренняя шина сообщений кластера Tomcat. Когда у вас несколько инстансов за балансировщиком, им нужно синхронизировать состояние сессий, иначе пользователь, попавший на другой узел после реплейса, будет разлогинен. Tribes занимается именно этим: передаёт сообщения между узлами кластера через отдельный порт (по умолчанию 4000), недоступный снаружи периметра.
Звучит как закрытая внутренняя магистраль — и именно поэтому её безопасности часто уделяют меньше внимания, чем веб-интерфейсу. Зря. Попасть на порт Tribes можно тремя путями: скомпрометировать любой хост в корпоративной сети, найти наименее защищённый сервер в самом кластере и использовать его как плацдарм, или — что встречается чаще чем кажется — через SSRF: если веб-приложение делает HTTP-запросы по URL из пользовательского ввода, атакующий просто указывает внутренний адрес и порт 4000.
КАК РАБОТАЕТ ОШИБКА
В CVE-2026-29146 проблема была в том, что EncryptInterceptor использовал CBC-шифрование без аутентификации сообщения. Это классический padding oracle: перехватывая зашифрованные пакеты и анализируя реакцию сервера на ошибки паддинга, атакующий мог методично восстановить открытый текст. Трудоёмко, но работает.
При исправлении разработчики переработали метод messageReceived() в классе EncryptInterceptor — но вынесли вызов super.messageReceived() за пределы блока try/catch. Вот как выглядит уязвимый код:
@Override
public void messageReceived(ChannelMessage msg) {
try {
byte[] data = msg.getMessage().getBytes();
data = encryptionManager.decrypt(data);
XByteBuffer xbb = msg.getMessage();
xbb.clear();
xbb.append(data, 0, data.length);
} catch (GeneralSecurityException gse) {
log.error(sm.getString("encryptInterceptor.decrypt.failed"), gse);
}
// Десериализация выполнится в любом случае — даже после ошибки расшифровки
super.messageReceived(msg);
}
Логика здесь сломана фундаментально. Если атакующий пришлёт сырые данные без шифрования, вызов decrypt() упадёт с BadPaddingException или IllegalBlockSizeException. Исключение перехватывается блоком catch, ошибка пишется в лог — и выполнение продолжается дальше. Вызов super.messageReceived(msg) уже вне блока try, поэтому он отработает в любом случае, передав оригинальные (нерасшифрованные) данные атакующего вниз по стеку.
Дальше данные попадают в GroupChannel, который передаёт их в XByteBuffer.deserialize(). Это стандартный ObjectInputStream Java — а это значит, что при десериализации автоматически вызываются методы вроде readObject() и hashCode() любых объектов, описанных в присланном payload.
КАК ЭТО ЭКСПЛУАТИРУЕТСЯ
Десериализация в Java опасна не сама по себе — опасны классы, которые есть в classpath сервера. Apache Commons Collections 3.x — практически стандартная зависимость для Java-приложений, и в ней давно известны цепочки гаджетов, ведущие прямо к выполнению произвольного кода.
Цепочка вызовов при использовании Commons Collections выглядит так:
HashSet.readObject()
→ HashMap.put(key, ...)
→ TiedMapEntry.hashCode()
→ LazyMap.get("poc")
→ ChainedTransformer.transform()
→ ConstantTransformer → Runtime.class
→ InvokerTransformer → Runtime.getMethod("getRuntime")
→ InvokerTransformer → Runtime.getRuntime()
→ InvokerTransformer → runtime.exec(cmd)
Инструмент ysoserial содержит восемь готовых вариаций для Commons Collections под разные версии библиотеки, плюс цепочки для Spring, Groovy, Commons BeanUtils и встроенных классов JDK. Задача атакующего — подобрать подходящую цепочку под конкретное окружение и отправить один пакет на порт Tribes.
Сравните с исходной CVE-2026-29146: там нужно было перехватывать трафик, методично зондировать сервер, анализировать ответы на ошибки паддинга. Здесь — один запрос, без аутентификации, без предварительного знания ключа шифрования.
РЕАЛЬНАЯ ЦЕПОЧКА АТАКИ
Атакующий находится внутри корпоративной сети — через скомпрометированный хост сотрудника, незащищённый Wi-Fi-сегмент или точку опоры на соседнем сервере кластера. Альтернативный вход — SSRF на веб-приложении: если приложение делает HTTP-запросы по URL из пользовательского ввода, атакующий направляет его на внутренний адрес и порт 4000. Порт Tribes не слушает снаружи, но изнутри — доступен.
Дальше всё просто. Атакующий определяет версию Tomcat через HTTP-заголовки или страницу ошибки, убеждается что Tribes активен (кластерная конфигурация подразумевает элемент <Cluster> в server.xml), генерирует payload через ysoserial с цепочкой под зависимости приложения — и отправляет один raw-пакет на порт 4000. Никакой аутентификации. Никакого ключа шифрования. Сервер сам вызывает нужные методы при десериализации.
Результат зависит от того, как запущен Tomcat. Без изоляции через systemd — это полная компрометация сервера одним пакетом, с правами процесса Tomcat. Если настроены PrivateTmp, NoNewPrivileges и ограничение capabilities — радиус поражения ограничен процессом, но RCE всё равно состоялся, и атакующий уже внутри.
ТАЙМЛАЙН
Исходная уязвимость CVE-2026-29146 — padding oracle в EncryptInterceptor — была исправлена в версиях 9.0.116, 10.1.53 и 11.0.20. Именно в этих версиях появился коммит 0112ed22, который вынес super.messageReceived() за пределы try/catch — и тем самым создал новую проблему.
26 марта 2026 года, вскоре после выхода патча, исследователь Bartlomiej Dmitruk из striga.ai сообщил команде безопасности Apache Tomcat о регрессии. Уведомление, судя по срокам, последовало практически сразу после выпуска — что говорит о целенаправленном аудите патча, а не случайном обнаружении.
9 апреля 2026 года опубликована CVE-2026-34486. В тот же день вышли исправленные версии 9.0.117, 10.1.54 и 11.0.21 с коммитом 776e12b3, восстановившим правильную обработку ошибок в messageReceived(). Публичный PoC от striga.ai появился вскоре после раскрытия.
ПОЧЕМУ ЭТО ВАЖНО
Это не абстрактная теоретическая угроза. Apache Tomcat — один из самых распространённых Java-серверов в мире, он используется в банках, платёжных системах, корпоративных порталах. Clustering с Tribes применяется везде, где нужна горизонтальная масштабируемость — то есть именно там, где нагрузка и ценность данных наиболее высоки.
Ситуацию усугубляет то, что многие команды могут считать себя защищёнными — они же обновились до 9.0.116 ради CVE-2026-29146. Оказывается, нет. Это создаёт ложное чувство безопасности хуже, чем если бы патча не существовало вовсе.
Отдельный урок — о природе регрессий. Исправление безопасности, внесённое без полного понимания потока выполнения, может создать уязвимость хуже исходной. CVE-2026-29146 требовала перехвата трафика и методичного подбора. CVE-2026-34486 — один пакет без аутентификации.
ЧТО ДЕЛАТЬ
Первым делом — понять, на какой версии вы вообще стоите. Если Tomcat установлен через пакетный менеджер, версия не всегда очевидна. Проверить можно так:
find / -name "catalina.jar" 2>/dev/null | xargs -I{} sh -c 'unzip -p "{}" org/apache/catalina/util/ServerInfo.properties 2>/dev/null | grep server.number'
Или через HTTP, если включена страница статуса:
curl -s http://localhost:8080/ | grep -i tomcat
Дальше — обновление. Здесь есть неочевидный момент: репозитории Debian/Ubuntu часто отстают от upstream на несколько минорных версий. Не факт что apt install tomcat9 даст вам 9.0.117 — сначала проверьте:
apt-cache policy tomcat9
Смотрите строку Candidate:. Если там не 9.0.117 или новее — обновляйте вручную с официальной страницы загрузки (tomcat.apache.org/download-90.cgi для ветки 9.x): остановить инстанс, заменить bin/ и lib/, оставив webapps/ и conf/ нетронутыми, запустить. Если репозиторий актуален — просто:
apt update && apt install tomcat9
После обновления убедитесь что версия действительно изменилась:
sh /path/to/tomcat/bin/version.sh | grep "Server version"
Должно быть 9.0.117, 10.1.54 или 11.0.21 — в зависимости от ветки. Если версия не изменилась, обновление не применилось.
Если обновить прямо сейчас нельзя — хотя бы изолируйте порт. Сначала убедитесь что Tribes вообще активен:
grep -n "Cluster" /opt/tomcat/conf/server.xml
ss -tlnp | grep 4000
Если порт слушает — закройте его через nftables, оставив доступ только доверенным узлам кластера:
nft add rule inet filter input tcp dport 4000 ip saddr != { 10.0.1.10, 10.0.1.11 } drop
Это временная мера, не замена патчу. Если Tribes вообще не используется — проверьте что элемент <Cluster> закомментирован или отсутствует в server.xml, и выключите его.
ВЫВОДЫ
CVE-2026-34486 — пример того, как заплатка сложнее болезни. Разработчики исправили padding oracle, но в результате логической ошибки в потоке выполнения создали уязвимость для уnauthenticated RCE. При этом пострадавшие версии — именно те, что считались безопасными после предыдущего патча.
Проверьте версию Tomcat прямо сейчас. Если вы на 9.0.116 — обновляйтесь до 9.0.117. Ветки 10.x и 11.x: 10.1.53 → 10.1.54, 11.0.20 → 11.0.21. Это не может ждать до следующего окна обслуживания, если порт Tribes хоть как-то доступен изнутри сети.
