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.
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 доступны для скачивания.
Присоединяйтесь к нам
Полезные ресурсы с нашими последними новостями и разработками.