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

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

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

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

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2022-22965 — 환경 설정, 디버그 과정별 설명, 익스플로잇 체인 분석을 포함한 CVE-2022-22965 (Spring4Shell)의 심층 기술 분석 (교육용 실습 목적). | Kitploit
도구/GitHubGitHub/khidottrivi/cve-2022-22965
Vulnerability AnalysisCode AnalysisExploitationWeb Application ExploitationLearning & EducationLabs & Practice
GitHubkhidottrivi/cve-2022-22965

CVE-2022-22965

환경 설정, 디버그 과정별 설명, 익스플로잇 체인 분석을 포함한 CVE-2022-22965 (Spring4Shell)의 심층 기술 분석 (교육용 실습 목적).

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

인기

모두 보기 →

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

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE 2022-22965_Spring4Shell 분석

취약점 설명

Spring4Shell은 Spring Framework의 Spring Core에 존재하는 CVE의 이름입니다.

CVSS 3.x 점수 9.8로, 이 취약점은 최고 위험 수준(critical)으로 분류됩니다. 이 취약점은 공격자가 원격 코드 실행을 수행하고 취약한 서버를 제어할 수 있게 합니다.

인터넷상의 Spring Core의 보편성과 Spring4Shell의 심각한 영향 수준을 고려할 때, 이 취약점은 전문가들에 의해 Log4shell에 못지않은 영향력을 가진 것으로 평가됩니다.

영향 범위

Spring4Shell은 인터넷상의 모든 Spring Framework 기반 웹 애플리케이션에 영향을 미치는 것이 아니라, 다음 조건을 만족하는 웹 애플리케이션에만 영향을 미칩니다.

  • 애플리케이션이 Spring Framework 버전 < 5.2, 5.2.0 – 5.2.19 또는 5.3.0 - 5.3.17을 사용하는 경우
  • 애플리케이션이 Spring-webmvc 또는 Spring-webflux 종속성 중 하나를 사용하는 경우
  • 애플리케이션이 JDK 버전 >= 9인 Java를 사용하는 경우
  • 애플리케이션이 전통적인 Java 웹 아카이브(.war 파일) 형태로 패키징되어 Tomcat에 배포된 경우 (Springboot로 실행되는 애플리케이션에서는 취약점이 아직 발견되지 않음)

환경 설정

제가 설정한 환경은 다음과 같습니다.

  • Spring Framework 5.1.0
  • Spring-webmvc 의존성 5.1.0
  • JDK 11.0.13 (Kali 2021.4a 가상 머신을 사용했으며, 이 Java 버전은 기본 설치되어 있음)
  • Apache Tomcat 9.0.45

취약점이 포함된 환경 및 프로젝트 생성, Intellij로 디버그 설정

  1. 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를 사용하세요.

  2. IDE 선택

    프로젝트를 코딩하고 .war 파일로 패키징하며, 가장 중요한 디버깅을 위해 IDE가 필요합니다. 저는 Intellij를 사용합니다. Eclipse나 Netbeans 등 Java를 지원하는 IDE라면 무엇이든 사용 가능합니다.

  3. 취약점이 포함된 간단한 프로젝트 생성

    제 프로젝트는 매우 간단하며, 다음으로 구성됩니다.

    • 모델 HelloWorld.java

      Untitled

    • 컨트롤러 HelloWorldController.java

      Untitled

    • 뷰 hello.jsp

      Untitled

  4. .war 파일 빌드

    프로젝트를 패키징하려면 다음을 수행합니다: Build -> Build Artifacts -> helloworld:war -> Build.

    빌드 프로세스가 성공적으로 완료될 때까지 기다립니다. 그러면 프로젝트에 out 폴더가 추가로 생성되며, ./out/artifacts/your_war_name/ 폴더에 your_war_name.war 파일이 있습니다. 이 .war 파일이 컴파일 및 패키징된 웹 프로젝트로, Apache Tomcat과 같은 Java Servlet에 배포하는 데 사용할 수 있습니다.

    Build Artifacts가 비활성화되어 있으면(빌드할 수 없는 경우) 이 프로젝트에 대해 Build Artifacts가 설정되지 않은 것입니다. 다음을 수행하세요: File -> Project Structure -> Artifacts -> 현재 모든 아티팩트 삭제 -> Add(+) -> Web Application: Exploded -> From Modules... -> OK (Exploded 생성 완료) -> Add(+) -> Web Application: Archive -> For 'helloworld:war exploded' -> OK. 그런 다음 Build Artifacts를 다시 수행합니다.

  5. 배포 및 디버그 설정

    • 배포

      .war 파일을 Apache Tomcat에 배포하려면 .war 파일을 Apache Tomcat 디렉토리 내의 /webapps 폴더에 복사하기만 하면 됩니다(예: 저의 경우 helloworld.war 파일(이름을 간단히 변경)을 /opt/tomcat/apache-tomcat-9.0.45/webapps/에 복사). 그런 다음 두 가지 방법으로 Tomcat 서버를 시작합니다(Linux 기준).

      • /opt/tomcat/apache-tomcat-9.0.45/bin/catalina.sh start (애플리케이션은 이 명령을 실행한 사용자 권한으로 실행되며, 저는 root입니다)
      • sudo service tomcat start (애플리케이션은 일반적으로 Tomcat 권한으로 실행되며, Apache Tomcat 설정 시 서비스 구성에 따라 다름)

      배포가 완료된 후, http://localhost:8080/helloworld에 접속합니다.

    • 디버그 설정

      Tomcat의 원격 디버그를 설정하려면 다음을 수행합니다.

      1. 서버 측:

        • catalina.sh 파일을 열고 JPDA_ADDRESS 매개변수의 localhost 값을 가상 머신 IP로 변경합니다.

          Untitled

        • 다음 명령으로 Tomcat 서버를 다시 시작합니다: /opt/tomcat/apache-tomcat-9.0.45/bin/catalina.sh jpda start. 이제 HTTP Server용 8080 포트 외에도 Tomcat이 8000 포트를 추가로 열어 디버그 연결을 허용합니다.

        참고: 이 디버그 부분은 Windows 10에서 Intellij를 실행하고 Kali 가상 머신에서 Tomcat을 실행하는 경우이므로 JPDA_ADDRESS를 변경해야 합니다. Intellij와 Tomcat을 동일한 기계에서 실행하는 경우 변경할 필요가 없습니다.

      2. Intellij 측:

        • Run -> Edit Configurations... -> Add(+) -> Remote JVM Debug

        • 이름 설정 -> Host와 Port를 catalina.sh 파일에서 수정한 IP와 포트로 변경 -> OK -> Shift + F9 (디버그 시작)

          Untitled

상세 분석

먼저 디버깅에 사용 중인 프로젝트를 분석하겠습니다. 위에서 언급했듯이 이 프로젝트는 단순히 다음으로 구성됩니다.

  • 모델 HelloWorld.java: HelloWorld 객체는 message(string)와 person(string) 두 속성과 setter/getter 함수를 가집니다(이처럼 단순한 구조이므로 이 객체를 Plain Old Java Object - POJO라고 합니다). 프로젝트에는 POJO 클래스가 있어야 하는데, 이는 Spring4Shell 취약점을 악용하기 위한 필수 조건입니다.
  • 컨트롤러 HelloWorldController.java: 이 클래스에는 helloPost 함수가 있으며, 입력 매개변수로 helloWorld(HelloWorld) 객체와 model(Model)을 받습니다. helloPost 함수는 helloWorld의 속성(person과 message) 값으로부터 model 객체에 addAttribute를 수행합니다. Spring4Shell을 악용하기 위한 두 번째 조건은 POJO 객체를 입력으로 받는 컨트롤러가 있어야 한다는 것입니다.
  • 뷰 hello.jsp: 이 hello.jsp 파일은 컨트롤러 HelloWorldController.java에서 전송된 model의 attribute를 호출하여 사용자에게 표시합니다.

예를 들어 보겠습니다.

Untitled

애플리케이션은 Post 요청의 params에서 정보를 가져와 helloWorld{“person”:”Leo”, “message”:”Hi there”} 객체를 생성합니다. 이 helloWorld 객체는 helloPost 함수의 입력으로 사용됩니다. 애플리케이션은 위에서 설명한 대로 작업을 수행하여 사용자에게 해당 응답을 반환합니다.

Post 요청 본문의 parameters에서 helloWorld 객체로 변환하는 과정은 전적으로 Spring에 의해 자동으로 수행됩니다. 그렇다면 Spring은 어떻게 이를 수행하며, 입력되는 parameters를 검증할까요?

소스(Source) 클래스 CachedIntrospectionResults

이 이미지는 디버깅 과정에서 캡처되었으며, 예시는 "class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT" 본문으로 요청을 수행한 것입니다. (왼쪽(스택 트레이스)을 (1), 오른쪽을 (2)라고 하겠습니다.)

Untitled

(1)에서 강조 표시된 부분을 살펴보면(아래에서 위로 올라가며), Spring은 Post 요청의 params에서 helloWorld 객체에 대해 applyPropertyValue를 수행합니다. 그리고 params가 단순히 person=Leo&message=Hi%20space와 같다면 Spring은 키워드 'person'이 helloWorld.person에, 'message'가 helloWorld.message에 해당함을 찾을 수 있습니다.

그러나 Spring은 HTTP 요청을 통해 객체를 전송하는 것도 허용합니다(객체를 HTTP로 보낸다고 말하는 것은 다소 과장되지만, 대략적으로 그렇게 이해할 수 있습니다). 제 person 속성이 더 이상 문자열이 아니라 Person 객체이고, Person 객체가 name(string)과 age(int) 두 하위 속성을 포함한다고 가정해 보겠습니다. 그러면 이 Person 객체에 대한 정보를 서버로 보내려면 "person.name=Leo&person.age=23"과 같이 보내야 합니다.

따라서 params의 형식은 A=X가 아니라 A.B.C.D… = X가 됩니다. A.B.C.D… = X 형식을 처리하기 위해, 예를 들어 A.B.C = X와 같은 param이 있다면 Spring은 간단히 다음과 같이 작업합니다: getA.getB.setC(X)로 변환합니다.

getA가 무엇인지 설명하지 않고, body 요청이 "person.name=Leo&person.age=23"인 경우를 예시로 들겠습니다. Spring은 helloWorld 객체에 person 속성과 getPerson 함수가 있는지 찾습니다. 있으면 Spring은 helloWorld.getPerson()을 호출하고, 이때 Spring은 Person 유형의 객체를 받습니다. 임시로 person1이라고 부르겠습니다. 그런 다음 Spring은 person1에 'name' 속성과 setName 함수가 있는지 확인합니다('name' 뒤에 '='가 있으므로). 있으면 Spring은 person1에 대해 setName(Leo)를 호출합니다.

person.age의 경우, Spring은 처음부터 person을 다시 찾지 않고 age로 바로 가지 않고 이전 객체(helloWorld와 person1)를 재사용합니다.

위 단계를 거치면 서버에는 helloWorld{person:{name:"Leo", age:23}} 객체가 있습니다(일단 message 속성은 무시).

그렇다면 Spring은 어떻게 각 객체의 속성을 찾을 수 있을까요? 예를 들어 helloWorld 객체의 'person' 속성 같은 것 말입니다.

(1)의 첫 부분에 CachedIntrospectionResults(beanClass) 함수가 있습니다. 이 함수는 beanClass의 속성을 나열합니다. (2)를 보면 beanClass가 model.HelloWorld일 때 세 가지 속성이 있습니다. 제가 만든 HelloWorld 모델에는 'person'과 'message' 두 속성만 있는데, 이 함수가 'class' 속성을 추가로 반환했습니다. 'class' 행을 확장하면 propertytype이 'java.lang.class'임을 알 수 있습니다.

따라서 java.lang.class 유형의 class 객체에 접근할 수 있습니다. 이것이 바로 Spring4Shell의 근원(source)입니다.

CVE-2010-1622

위 source와 관련하여, 이 source와 관련된 CVE-2010-1622가 이미 있었습니다. CVE-2010-1622의 저자는 class.classLoader.URLs[0] = X 페이로드를 사용하여 이 source를 악용했습니다.

java.lang.class에는 getClassLoader() 함수가 있어 ClassLoader 객체를 반환하고, 이 ClassLoader는 Tomcat의 URLs 배열(리소스를 로드하는 데 사용)에 접근할 수 있기 때문입니다. URLs에 접근할 수 있게 되면 공격자는 URLs[0] 값을 악성 Jar 파일(공격자가 제어)에 대한 원격 URL로 변경할 수 있습니다.

이 취약점을 해결하기 위해 Spring은 CachedIntrospectionResults(beanClass) 함수에서 필터(블랙리스트)를 구현했습니다.

Untitled

'beanClass' == Class.class(java.lang.class)인 경우 pd는 'classLoader'와 'protectionDomain'과 달라야 합니다. 그리고 CachedIntrospectionResults가 java.lang.class의 모든 속성을 로드한 후 두 속성 'classLoader'와 'protectionDomain'이 없는 것이 증거입니다.

Untitled

그러나 JDK 9부터 Class.class에 'module' 속성이 추가되었고, Class.module에는 classLoader 속성이 있습니다.

Untitled

→ 따라서 JDK 9 이상을 사용하면 Spring의 블랙리스트를 우회할 수 있습니다!!!

싱크(Sink) 클래스 AccessLogValue

Spring4Shell 취약점의 공개 PoC에 따르면 사용된 페이로드는 다음과 같은 형식입니다.

class.module.classloader.resources.context.parent.pipeline.first ⇔ Class.getModule().getClassLoader().getResources().getContext().getParent().getPipeline().getFirst()

디버깅을 통해 다음 gadget chain을 확인할 수 있습니다.

java.lang.class.getModule() -> java.lang.module.getClassLoader() -> org.apache.catalina.loader.ParallelWebappClassLoader.getResources() -> org.apache.catalina.webresources.StandardRoot.getContext() -> org.apache.catalina.core.StandardContext.getParent() -> org.apache.catalina.core.StandardHost.getPipeline() -> org.apache.catalina.core.StandardPipeline.getFirst() -> org.apache.catalina.valves.AccessLogValue.

AccessLogValue 클래스에는 다음과 같은 속성이 있습니다.

Untitled

AccessLogValue 객체를 호출할 수 있으며, 이 AccessLogValue는 Tomcat의 로그 기록에 영향을 미칩니다.

→ Tomcat 서버에서 AccessLogValue 객체의 속성을 설정하여 서버에 파일을 생성할 수 있습니다. 이를 위해 PoC에서는 Prefix, Suffix, Pattern, Directory 및 fileDateFormat 속성을 설정했습니다. 페이로드 요청은 다음과 같습니다.

"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="

  • 중단점 목록

    디버깅 과정을 더 쉽게 하기 위해 다음 지점에 중단점을 설정할 수 있습니다.

    Untitled

결론

이 분석에는 원래 결론 부분이 없습니다. 이 부분은 그냥 넣어두기 위해 추가한 것입니다!!!

해결 방법을 찾고 있다면 여기 있습니다.

도구 다운로드