Spring Framework RCE分析

0x01 前言

  因为第一次调试spring框架,首先分析spring的参数绑定,从预警和后来的poc来看,和spring的参数绑定有关系

0x02参数绑定流程

  先记录几个单词的意思

1
2
3
4
5
Handler:    ['hændlə] 处理者、处理程序
Disptacher: [dis'pætʃə] 调度员、调度程序
Mapping: ['mæpiŋ] 映射
Adapter: [ə'dæptə] 适配器
Argument: ['ɑ:ɡjumənt] 逻辑论证

1. Spring运行流程图

image-20220403000622503

2. 统一调度方法

  先找到统一的调度器方法,并设置断点org/springframework/web/servlet/DispatcherServlet.class#doDispatch()
图中三个断点位置

  • 第一个this.checkMultipart() ,这里看到就断下来了,我记得之前有个漏洞好像和这个有关系。判断是否是上传包
  • 第二个断点是HandlerAdapter的初始化;这里看到下面还有一个判断是否是Get或HEAD方法,并判断是否支持这个方法
  • 第三个断点即进入HandlerAdapter的处理方法,HandlerAdapter.handle
image-20220403000650938

3. 处理适配器的处理方法

  步入上面ha.handle()的处理方法中,通过this.invokeHandlerMethod()方法获取熟悉的ModelAndView对象

image-20220403000717238

4. 调用与处理

  继上一步,进入org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.invokeHandlerMethod ,图中断点位置进入invokeAndHandle(调用和处理)方法,断点前是一些ModelAndViewContainer和AsyncWebRequest对象的初始化

image-20220403000749580

  接着进入invokeAndHandle方法后,首先执行的就是invokeForRequest

![image-20220403000808200](/img/Spring Framework RCE分析/image-20220403000808200.png)

  下一步进入invokeForRequest方法,这里从字面上可以看出下一步是获取方法的值(this.getMethodArgumentValues)

image-20220403000823543

5. 进入参数绑定

a. 进入上一步的 getMethodArgumentValues()this.resolvers.resolveArgument 方法设置断点

image-20220403000839778

b. 进入该方法,执行到

org/springframework/web/method/annotation/ModelAttributeMethodProcessor#resolveArgument 在图中断点位置,可以看到bindRequestParameters(),可以把它理解为”绑定请求参数”

image-20220403000911356

c. 指定到DataBinder#doBind()方法,进入this.applyPropertyValues()

image-20220403000954194 image-20220403001037708

d. 跟入setPropertyValue 方法,可以看到nestedPa是一个封装了Object[POJO.Student]的BeanWrapperImpl对象

image-20220403001137387

0x02 漏洞分析

  这里笔记分割的比较突然,主要是因为下面的流程就慢慢和漏洞有关联

1. 参数名的分割处理

a. 先是接上上面参数绑定的执行流程中,setPropertyValue方法,如图所示通过

image-20220403001152332

b. 通过PropertyAccessorUtils.getFirstNestedPropertySeparatorIndex(propertyPath) 计算pos为5,查看getFirstNestedPropertySeparatorIndex计算的方法,这里是计算第一个字符到第一个. 中间字符的个数,再进行分割,如class.x pos就是5,去除class

  • 关键位置还是this.getNestedPropertyAccessor(nestedProperty)在循环处理

    image-20220403001209942

2. 参数绑定的意外情况

  • 继续调试,在参数绑定的流程中,发现除了当前POJO对象的一些字段以外,多了一个class字段
  • 这里为了防止混淆,重新提交id.x=666&name=nine这样类型的参数,继续调试,进入getNestedPropertyAccessor方法

a. 继续步入,程序通过getPropertyValue获取一个Object,tokens在前面是nestedProperty的格式化效验,也就是参数中的id

image-20220403001226852

b. 进入该方法,指定到getLocalPropertyHandler,关注一下这段代码的执行

image-20220403001303500
  • getPropertyDescriptor 这个地方可以画重点了,这里可以看到,获取propertyDescriptorCache 这个Map对象中的值,key对应这POJO中的字段,但是这里多了一个class键值对。
image-20220403001419499

​ 带着class这个键值对的疑惑,回到上一步CachedIntrospectionResults 中,可以看到strongClassCache 中每一个Value对象的propertyDescriptorCache都带有这个class键值对

image-20220403001419499

3. 发送class.x参数重新调试

  • 此时在post包中输入class.x=test参数调试进一步分析

a. 在获取了getPropertyValue()即POJO.Student后,回到getNestedPropertyAccessor 方法,继续执行到this.newNestedPropertyAccessor(value, this.nestedPath + canonicalName + ".");

image-20220403001448066

b. 实例化 newNestedPropertyAccessor 方法,赋值给nestedPa,并再次进入PropertyAccessorUtils.getFirstNestedPropertySeparatorIndex(propertyPath) 的循环调用中,new newNestedPropertyAccessor实例化的一个新对象是包装java.lang.Class的BeanWrapperImpl类

image-20220403001504964

c. 可以看到之前的循环处理x.x.x.x参数名的代码,新的nestedPath,继续调用getFirstNestedPropertySeparatorIndex(propertyPath) 方法

image-20220403001525042
 分析到这里,已经可以发现spring在参数绑定时,对于正常的参数和x.x=aaa这种参数的不同处理

04. JDK与Bypass

​ 回头看一下CVE-2010-1622发现看的顺畅多了,但是class.classloader用不了了,这里切换到jdk8,在包装对象转换流程中看一下,发现JDK9+多了一个module属性。

  • JDK8.x获取到的class

    image-20220403001541124
    • JDK9+

      image-20220403001557353

​ Class.module是JDK9+后的新特性,JPMS模块化,暂时理解是将以前的classpath的这种统一调用的方法,可扩展为模块化调用

0x03 利用链构造

a. 首先在strongClassCache中看一下Class类的propertyDescriptoCache有哪些可以调用的GenericTypeAwarePropertyDescriptor对象

image-20220403001616324

b. 使用class.module.x=test作为参数,请求,在nestedPa这个地方,获取了类型为java.lang.Module的包装类

image-20220403001634997

c. 继续构造class.module.x.x,打开propertyDescriptoCache,看到了classLoader

image-20220403001653551

d. 这里也就对应java.lang.Module类中,成员的get方法

image-20220403001715044

e. 这里获取的classLoader是tomcat的类加载器org.apache.catalina.loader.ParallelWebappClassLoader

org.springframework.beans.BeanWrapperImpl: wrapping object [org.apache.catalina.loader.ParallelWebappClassLoader@71d41616]

image-20220403001733958

f.参考S2-020在tomcat8中的利用链思路

1
2
3
4
5
6
7
tomcat中的利用链
org.apache.catalina.loader.ParallelWebappClassLoader
org.apache.catalina.webresources.StandardRoot
org.apache.catalina.core.StandardContext
org.apache.catalina.core.StandardHost
org.apache.catalina.core.StandardPipeline
org.apache.catalina.valves.AccessLogValve
image-20220403001757430

​ 最终的payload也是通过操作tomcat启动后AccessLogValve的对象,指定日志输出位置,并填充日志内容,写入WebShell

01 相对路径问题

 这里在debug的时候总是写不到相对目录,后来在调试过程中,发现idea直接部署的项目会临时将配置放在一个缓存路径下,而真实的tomcat部署的项目,可以直接通过webapps/ROOT使用相对路径
1
class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT
image-20220403001813935

​ AccessLogValve中会新建日志文件及路径涉及的文件夹,也就是说指定webapps/自定义一个文件夹就可以,不需要必须有ROOT

image-20220403001830287
 AccessLogValve 在getLogFile中会新建目录
image-20220403001849265

02. 多次上传的问题

​ 第一次发包文件落地后,第二次发包,默认是不新建文件的,这个时候通过指定fileDateFormat=3,tomcat会新建一个文件,文件格式是prefix + fileDateFormat + suffix,fileDateFormat需要满足格式,这里随便输入数字即可

1
2
class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=1
class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=2
image-20220403001924129

03. 特殊符号的问题

​ 熟悉access日志的还记得tomcat在配置日志时,通过%h %l %u等获取tomcat解析http后的内置字段,因此pattern中的%会被解析

image-20220403002011555

​ 因此需要通过%{headerName}i,获取自定义header字段,通过这种方式将%拼接在pattern中

image-20220403002037838

04. SpringBoot

​ SpringBoot中通过class → module → classLoader获取的是应用程序类加载器AppClassLoader;可继续使用的propertyDescriptors很有限,目前没有什么思路

image-20220403002059502

0x04 阶段总结

  1. 该漏洞和spring在3月29号更新的反序列化漏洞没有关系,默认spring也没有导入org.springframework.cache ,这次更新修复的是2月19号的一个pr

    image-20220403002119066
  2. 目前的利用链

    1
    2
    3
    spring beans中的参数绑定中存在一个class属性
    - 通过jdk9module特性,即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