Spring4shell利用条件

  1. JDK >= 9
  2. spring框架 (涉及依赖包:spring-beans<5.3.18)
  3. tomcat服务器

漏洞原理:

该漏洞利用了javabean的构造过程,通过高版本jdk及tomcat利用链触发漏洞

BeanWrapperImpl类

BeanWrapper类提供分析和处理标准javabean(具备setter/getter方法的属性类)的功能,可以获取属性类描述,以及属性类的参数的赋值与获取。

BeanWrapperImpl类的的类关系图

1.png

PropertyEditorRegistrySupport提供了类型转换的标准

TypeConverter提供了类型转换的统一接口

ConfigurablePropertyAccessor主要实现了javabean的set/get操作接口

内省类及PropertyDescriptor

内省简单理解就是对java反射进行了封装,方便对javabean进行set/get操作

BeanWrapperImpl类就是利用PropertyDescriptor类进行javabean的get/set操作

CachedIntrospectionResults

对对象的进行内省分析,保存对象的PropertyDescriptor信息,并缓存了所有的bean的信息

漏洞调试过程

断点:

  • org/springframework/validation/DataBinder.java

    截屏2022-05-13 下午7.33.14.png

  • org/springframework/beans/BeanWrapperImpl.java (setValue方法)(反射设置值)

    截屏2022-05-13 下午7.34.15.png

  • org/springframework/beans/CachedIntrospectionResults.java

  1. private CachedIntrospectionResults(Class<?> beanClass) throws BeansException {
  2. try {
  3. if (logger.isTraceEnabled()) {
  4. logger.trace("Getting BeanInfo for class [" + beanClass.getName() + "]");
  5. }
  6. this.beanInfo = getBeanInfo(beanClass);
  7. if (logger.isTraceEnabled()) {
  8. logger.trace("Caching PropertyDescriptors for class [" + beanClass.getName() + "]");
  9. }
  10. this.propertyDescriptorCache = new LinkedHashMap<>();
  11. //断点标记
  12. PropertyDescriptor[] pds = this.beanInfo.getPropertyDescriptors();
  13. for (PropertyDescriptor pd : pds) {
  14. // 新的漏洞利用jdk的特性进行绕过 class.classLoader => class.module.classLoader
  15. if (Class.class == beanClass &&
  16. ("classLoader".equals(pd.getName()) || "protectionDomain".equals(pd.getName()))) {
  17. continue;
  18. }
  19. ...
  • org/springframework/beans/AbstractNestablePropertyAccessor.java
  1. private void processLocalProperty(PropertyTokenHolder tokens, PropertyValue pv) {
  2. // 根据属性name获取到对应javabean填充类
  3. PropertyHandler ph = getLocalPropertyHandler(tokens.actualName);

1、搭建环境,开启远程调试

3.png
4.png

2、进入到CachedIntrospectionResults.java类中(只有第一次加载name时会进入该类),调用了propertyDescriptors类不断读取javabean对象的参数。
一个javabean除了包括自身的参数还继承了object类的参数class

5.png

3、断点调试进入下一个beanclass==java.lang.Class,可以看到class有很多个参数,其中有一个是module参数,(jdk9+的特性可以通过class.module.classloader操作其他类)这个也是绕过的关键。
7.png

4、往下继续调试,直到拼接出传递的一个完整的参数,根据最后一个参数进行查找
9.png

5、所有的参数都存放在propertyDescriptorCache缓存中,根据directory查找到accessLogValue这个javabean,然后将传递的参数值赋值
10.png
11.png

6、后续参数的赋值同理,最终经过转换将传递的值赋值给AccessLogValue这个javabean

(对应 conf 目录下的 server.xml 文件)

  1. <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="access." suffix=".log"
  2. pattern="%h %l %u %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %{X-Forwarded-For}i "%Dms"" resolveHosts="false"/>

最终在内存中调用AccessLogValue类达到写入shell。

ClassLoader能加载操作到的类

2.png

漏洞修复建议:

1、WAF规则*临时修复

增加waf规则建立虚拟补丁,过滤”class.”, “Class.”, “.class.”, and “.Class.”,在生产部署前验证测试不会影响正常业务运转

2、Spring框架控制器建议*临时修复

使用Spring框架来禁止某些模式,需要重新编译部署项目。

import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.InitBinder; @ControllerAdvice @Order(10000) public class BinderControllerAdvice { @InitBinder public void setAllowedFields(WebDataBinder dataBinder) { String[] denylist = new String[]{"class.", "Class.", ".class.", ".Class."}; dataBinder.setDisallowedFields(denylist); } }

3、升级框架

spring-core>=v5.3.19

spring-beans>=v5.3.18

spring-beans官方修复代码v5.3.19(部分)

6.png

参考

https://tttang.com/archive/1532/#toc_0x03

https://cloud.tencent.com/developer/article/1035297

https://blog.csdn.net/qq_25179481/article/details/97620204