Spring Framework RCE分析
0x01 前言
因为第一次调试spring框架,首先分析spring的参数绑定,从预警和后来的poc来看,和spring的参数绑定有关系
0x02参数绑定流程
先记录几个单词的意思
1 | Handler: ['hændlə] 处理者、处理程序 |
1. Spring运行流程图
2. 统一调度方法
先找到统一的调度器方法,并设置断点org/springframework/web/servlet/DispatcherServlet.class#doDispatch()
图中三个断点位置
- 第一个
this.checkMultipart()
,这里看到就断下来了,我记得之前有个漏洞好像和这个有关系。判断是否是上传包 - 第二个断点是
HandlerAdapter
的初始化;这里看到下面还有一个判断是否是Get或HEAD方法,并判断是否支持这个方法 - 第三个断点即进入
HandlerAdapter
的处理方法,HandlerAdapter.handle
3. 处理适配器的处理方法
步入上面ha.handle()的处理方法中,通过this.invokeHandlerMethod()
方法获取熟悉的ModelAndView对象
4. 调用与处理
继上一步,进入org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.invokeHandlerMethod ,图中断点位置进入invokeAndHandle
(调用和处理)方法,断点前是一些ModelAndViewContainer和AsyncWebRequest对象的初始化
接着进入invokeAndHandle方法后,首先执行的就是invokeForRequest
![image-20220403000808200](/img/Spring Framework RCE分析/image-20220403000808200.png)
下一步进入invokeForRequest方法,这里从字面上可以看出下一步是获取方法的值(this.getMethodArgumentValues)
5. 进入参数绑定
a. 进入上一步的 getMethodArgumentValues()
在this.resolvers.resolveArgument
方法设置断点
b. 进入该方法,执行到
org/springframework/web/method/annotation/ModelAttributeMethodProcessor#resolveArgument 在图中断点位置,可以看到bindRequestParameters()
,可以把它理解为”绑定请求参数”
c. 指定到DataBinder#doBind()方法,进入this.applyPropertyValues()
d. 跟入setPropertyValue
方法,可以看到nestedPa是一个封装了Object[POJO.Student]的BeanWrapperImpl对象
0x02 漏洞分析
这里笔记分割的比较突然,主要是因为下面的流程就慢慢和漏洞有关联
1. 参数名的分割处理
a. 先是接上上面参数绑定的执行流程中,setPropertyValue
方法,如图所示通过
b. 通过PropertyAccessorUtils.getFirstNestedPropertySeparatorIndex(propertyPath) 计算pos为5,查看getFirstNestedPropertySeparatorIndex计算的方法,这里是计算第一个字符到第一个. 中间字符的个数,再进行分割,如class.x pos就是5,去除class
关键位置还是
this.getNestedPropertyAccessor(nestedProperty)
在循环处理
2. 参数绑定的意外情况
- 继续调试,在参数绑定的流程中,发现除了当前POJO对象的一些字段以外,多了一个class字段
- 这里为了防止混淆,重新提交
id.x=666&name=nine
这样类型的参数,继续调试,进入getNestedPropertyAccessor
方法
a. 继续步入,程序通过getPropertyValue获取一个Object,tokens在前面是nestedProperty的格式化效验,也就是参数中的id
b. 进入该方法,指定到getLocalPropertyHandler
,关注一下这段代码的执行
- getPropertyDescriptor 这个地方可以画重点了,这里可以看到,获取
propertyDescriptorCache
这个Map对象中的值,key对应这POJO中的字段,但是这里多了一个class
键值对。
带着class这个键值对的疑惑,回到上一步CachedIntrospectionResults 中,可以看到strongClassCache 中每一个Value对象的propertyDescriptorCache
都带有这个class键值对
3. 发送class.x参数重新调试
- 此时在post包中输入class.x=test参数调试进一步分析
a. 在获取了getPropertyValue()即POJO.Student后,回到getNestedPropertyAccessor 方法,继续执行到this.newNestedPropertyAccessor(value, this.nestedPath + canonicalName + ".");
b. 实例化 newNestedPropertyAccessor 方法,赋值给nestedPa,并再次进入PropertyAccessorUtils.getFirstNestedPropertySeparatorIndex(propertyPath) 的循环调用中,new newNestedPropertyAccessor
实例化的一个新对象是包装java.lang.Class的BeanWrapperImpl类
c. 可以看到之前的循环处理x.x.x.x参数名的代码,新的nestedPath,继续调用getFirstNestedPropertySeparatorIndex(propertyPath)
方法
分析到这里,已经可以发现spring在参数绑定时,对于正常的参数和x.x=aaa这种参数的不同处理
04. JDK与Bypass
回头看一下CVE-2010-1622发现看的顺畅多了,但是class.classloader用不了了,这里切换到jdk8,在包装对象转换流程中看一下,发现JDK9+多了一个module属性。
JDK8.x获取到的class
JDK9+
Class.module是JDK9+后的新特性,JPMS模块化,暂时理解是将以前的classpath的这种统一调用的方法,可扩展为模块化调用
0x03 利用链构造
a. 首先在strongClassCache中看一下Class类的propertyDescriptoCache有哪些可以调用的GenericTypeAwarePropertyDescriptor
对象
b. 使用class.module.x=test
作为参数,请求,在nestedPa这个地方,获取了类型为java.lang.Module的包装类
c. 继续构造class.module.x.x,打开propertyDescriptoCache,看到了classLoader
d. 这里也就对应java.lang.Module
类中,成员的get方法
e. 这里获取的classLoader是tomcat的类加载器org.apache.catalina.loader.ParallelWebappClassLoader
类
org.springframework.beans.BeanWrapperImpl: wrapping object [org.apache.catalina.loader.ParallelWebappClassLoader@71d41616]
f.参考S2-020在tomcat8中的利用链思路
1 | tomcat中的利用链 |
最终的payload也是通过操作tomcat启动后AccessLogValve
的对象,指定日志输出位置,并填充日志内容,写入WebShell
01 相对路径问题
这里在debug的时候总是写不到相对目录,后来在调试过程中,发现idea直接部署的项目会临时将配置放在一个缓存路径下,而真实的tomcat部署的项目,可以直接通过webapps/ROOT使用相对路径
1 | class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT |
AccessLogValve中会新建日志文件及路径涉及的文件夹,也就是说指定webapps/自定义一个文件夹就可以,不需要必须有ROOT
AccessLogValve 在getLogFile中会新建目录
02. 多次上传的问题
第一次发包文件落地后,第二次发包,默认是不新建文件的,这个时候通过指定fileDateFormat=3,tomcat会新建一个文件,文件格式是prefix + fileDateFormat + suffix
,fileDateFormat需要满足格式,这里随便输入数字即可
1 | class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=1 |
03. 特殊符号的问题
熟悉access日志的还记得tomcat在配置日志时,通过%h %l %u等获取tomcat解析http后的内置字段,因此pattern中的%会被解析
因此需要通过%{headerName}i
,获取自定义header字段,通过这种方式将%拼接在pattern中
04. SpringBoot
SpringBoot中通过class → module → classLoader获取的是应用程序类加载器AppClassLoader
;可继续使用的propertyDescriptors很有限,目前没有什么思路
0x04 阶段总结
该漏洞和spring在3月29号更新的反序列化漏洞没有关系,默认spring也没有导入org.springframework.cache ,这次更新修复的是2月19号的一个pr
目前的利用链
1
2
3spring beans中的参数绑定中存在一个class属性
- 通过jdk9的module特性,即class中存在module属性,bypass CVE-2010-1622,调用到ClassLoader
- 通过操作org.apache.catalina.valves.AccessLogValve,指定日志路径并触发日志写入的操作,传入了webshell
整个分析过程中,其实感觉还有利用链可以挖掘,不管这个漏洞危害如何,感觉很有意思;
同时也思考一个问题,挖个漏洞的时候该怎么思考?
我觉得首先应该有CVE-2010-1622和S2-020的分析经验吧,同时也了解过JDK9+的新特性,或者在调试老洞的时候误用了新的JDK
真牛
在spring框架调试中感到窒息
参考
Spring参数绑定:https://blog.csdn.net/AlbenXie/article/details/108548786