Tomcat-CVE-2025-24813

CVE-2025-24813 как multi-step уязвимость

Нахождение multi-step уязвимостей для большинства современных анализаторов является нетривиальной задачей. Как правило, производится анализ одного потока от source до sink, без использования информации о том что происходит в других потоках. Для поддержки multi-step анализа необходимо:

  • определить persistent object, persistent sink и persistent transfer для приложения
  • среди путей от source до sink выделить проходящие через persistent transfer и подтвердить их security-тестом
  • скомбинировать уязвимости, соответствующие security-тестам с общими persistent transfer
  • установить связи между найденными уязвимыми методами Apache Tomcat и обработчиками пользовательских запросов

Рассмотрим как можно найти эту уязвимость в Apache Tomcat целиком, если рассматривать её как multi-step. Увидим, что на практике даже такие сложные приложения как Apache Tomcat, можно анализировать по частям и подтвердить уязвимость с помощью security-теста.

Декомпозируем Tomcat на компоненты, для которых будем искать пути и генерировать security-тесты.

Выбор persistent object

persistent object - это глобальный объект, в который пишутся и читаются данные, и сохраняющий свое состояние между запросам. Необходимо задать persistent-объект и методы работающие с ним:

  • persistent sink, изменяет состояние persistent object
  • persistent transfer, возвращает значения из persistent object

В Apache Tomcat, настройки сервера находятся в свойстве attributes класса org.apache.catalina.core.ApplicationContext, выберем его в качестве persistent object. Метод org.apache.catalina.core.ApplicationContext.setAttribute это persistent sink, метод org.apache.catalina.core.ApplicationContext.getAttribute это persistent transfer.

Занесем эту информацию в базу знаний о persistent object.

Path I: Restricted File Write

Находим путь от метода org.apache.catalina.servlets.DefaultServlet.doPut(HttpServletRequest, HttpServletResponse), до JDK метода java.io.RandomAccessFile.<init>(File file, String mode), проходящий через persistent transfer. Генерируем security-тест подтверждающий этот путь.

Видим, что используя persistent transfer, читается значение атрибута "javax.servlet.context.tempdir"

Доходим до метода executePartialPut и производим запись в файл в директории, полученной из атрибута "javax.servlet.context.tempdir". Запоминаем условие: произвольная запись в файл в директории, полученной из persistent object.

tomcat-CVE-2025-24813

Path II: Unsafe Deserialization

Другой путь проходящий через persistent transfer, это путь от public-метода org.apache.catalina. session.PersistentManager.findSession(String) до JDK метода java.io.ObjectInputStream.<init>(InputStream)

Сессия Tomcat ищется в директории полученной из атрибута "javax.servlet.context.tempdir".

Содержимое файла с сессией десериализуется методом java.io.ObjectInputStream.<init>(InputStream).

Запоминаем условие: десериализация файла в директории, полученной из persistent object.

Комбинирование security-тестов

Два рассмотренных пути до JDK sink, используют атрибут "javax.servlet.context.tempdir", полученный из persistent object.

Комбинация условий для этих путей, произвольная запись в файл в директории и десериализация файла, приводят к уязвимости Code Execution.

Обработка пользовательских запросов в Apache Tomcat

Начальный метод для анализа (source) должен быть связан с HTTP-запросом приходящим от пользователя. Рассмотри компонент обработки сервлетных фильтров в Apache Tomcat. Фильтр — это объект, пригодный для повторного использования и позволяющий преобразовать содержание HTTP-запросов, прежде чем тот попадает в сервлет. Фильтр также может использоваться для последующей обработки ответа, исходящего из сервлета. Рассмотрим подробнее метод ApplicationFilterChain.internalDoFilter в котором реализуется механизм фильтров.

Callstack до метода ApplicationFilterChain.internalDoFilter:

                internalDoFilter(ServletRequest, ServletResponse):199, ApplicationFilterChain (org.apache.catalina.core), ApplicationFilterChain.java
                doFilter(ServletRequest, ServletResponse):144, ApplicationFilterChain (org.apache.catalina.core), ApplicationFilterChain.java
                doFilter(ServletRequest, ServletResponse, FilterChain):51, WsFilter (org.apache.tomcat.websocket.server), WsFilter.java
                internalDoFilter(ServletRequest, ServletResponse):168, ApplicationFilterChain (org.apache.catalina.core), ApplicationFilterChain.java
                doFilter(ServletRequest, ServletResponse):144, ApplicationFilterChain (org.apache.catalina.core), ApplicationFilterChain.java
                invoke(Request, Response):168, StandardWrapperValve (org.apache.catalina.core), StandardWrapperValve.java
                invoke(Request, Response):90, StandardContextValve (org.apache.catalina.core), StandardContextValve.java
                invoke(Request, Response):482, AuthenticatorBase (org.apache.catalina.authenticator), AuthenticatorBase.java
                invoke(Request, Response):130, StandardHostValve (org.apache.catalina.core), StandardHostValve.java
                invoke(Request, Response):93, ErrorReportValve (org.apache.catalina.valves), ErrorReportValve.java
                invoke(Request, Response):660, AbstractAccessLogValve (org.apache.catalina.valves), AbstractAccessLogValve.java
                invoke(Request, Response):74, StandardEngineValve (org.apache.catalina.core), StandardEngineValve.java
                service(Request, Response):346, CoyoteAdapter (org.apache.catalina.connector), CoyoteAdapter.java
                service(SocketWrapperBase):396, Http11Processor (org.apache.coyote.http11), Http11Processor.java
                process(SocketWrapperBase, SocketEvent):63, AbstractProcessorLight (org.apache.coyote), AbstractProcessorLight.java
                process(SocketWrapperBase, SocketEvent):937, AbstractProtocol$ConnectionHandler (org.apache.coyote), AbstractProtocol.java
                doRun():1791, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net), NioEndpoint.java
                run():52, SocketProcessorBase (org.apache.tomcat.util.net), SocketProcessorBase.java
                runWorker(ThreadPoolExecutor$Worker):1190, ThreadPoolExecutor (org.apache.tomcat.util.threads), ThreadPoolExecutor.java
                run():659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads), ThreadPoolExecutor.java
                run():63, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads), TaskThread.java
                run():750, Thread (java.lang), Thread.java

После преобразований над входным запросом, в строке 123 вызывается метод javax.servlet.http.HttpServlet.service(ServletRequest, ServletResponse), аргументы request и response содержат пользовательские запрос и ответ.

Этот метод удобно взять для генерации security-теста. Компоненты приложения Tomcat уже были проанализированы, поэтому генерировать security-тест от данного метода javax.servlet.http.HttpServlet.service(ServletRequest, ServletResponse), необходимо до тех методов, для которых уже были ранее построены security-тесты, использующие persistent object. Новые security-тесты показывают, что уязвимость в Tomcat можно воспроизвести с помощью HTTP-запроса.

Tomcat это сложное приложение с множеством пользовательских настроек и конфигурационных файлов. В сгенерированом security-тесте мы работаем с объектами javax.servlet.http.HttpServlet, HttpServletRequest, HttpServletResponse. Возможно задать поля этих объектов так, чтобы пройти все условия до вызова методов executePartialPut и findSession. Например, задать необходимые заголовки HTTP-запроса (Content-Range) или специальные настройки конфигурации (allowPartialPut = True).

Сгенерированные security-тесты для CVE-2025-24813 доступны для скачивания.

Присоединяйтесь к нам

Полезные ресурсы с нашими последними новостями и разработками.