Spring4shell利用条件
- JDK >= 9
- spring框架 (涉及依赖包:spring-beans<5.3.18)
- tomcat服务器
漏洞原理:
该漏洞利用了javabean的构造过程,通过高版本jdk及tomcat利用链触发漏洞
BeanWrapperImpl类
BeanWrapper类提供分析和处理标准javabean(具备setter/getter方法的属性类)的功能,可以获取属性类描述,以及属性类的参数的赋值与获取。
BeanWrapperImpl类的的类关系图
PropertyEditorRegistrySupport提供了类型转换的标准
TypeConverter提供了类型转换的统一接口
ConfigurablePropertyAccessor主要实现了javabean的set/get操作接口
内省类及PropertyDescriptor
内省简单理解就是对java反射进行了封装,方便对javabean进行set/get操作
BeanWrapperImpl类就是利用PropertyDescriptor类进行javabean的get/set操作
CachedIntrospectionResults
对对象的进行内省分析,保存对象的PropertyDescriptor信息,并缓存了所有的bean的信息
漏洞调试过程
断点:
org/springframework/validation/DataBinder.java
org/springframework/beans/BeanWrapperImpl.java (setValue方法)(反射设置值)
org/springframework/beans/CachedIntrospectionResults.java
private CachedIntrospectionResults(Class<?> beanClass) throws BeansException {
try {
if (logger.isTraceEnabled()) {
logger.trace("Getting BeanInfo for class [" + beanClass.getName() + "]");
}
this.beanInfo = getBeanInfo(beanClass);
if (logger.isTraceEnabled()) {
logger.trace("Caching PropertyDescriptors for class [" + beanClass.getName() + "]");
}
this.propertyDescriptorCache = new LinkedHashMap<>();
//断点标记
PropertyDescriptor[] pds = this.beanInfo.getPropertyDescriptors();
for (PropertyDescriptor pd : pds) {
// 新的漏洞利用jdk的特性进行绕过 class.classLoader => class.module.classLoader
if (Class.class == beanClass &&
("classLoader".equals(pd.getName()) || "protectionDomain".equals(pd.getName()))) {
continue;
}
...
- org/springframework/beans/AbstractNestablePropertyAccessor.java
private void processLocalProperty(PropertyTokenHolder tokens, PropertyValue pv) {
// 根据属性name获取到对应javabean填充类
PropertyHandler ph = getLocalPropertyHandler(tokens.actualName);
1、搭建环境,开启远程调试
2、进入到CachedIntrospectionResults.java类中(只有第一次加载name时会进入该类),调用了propertyDescriptors类不断读取javabean对象的参数。
一个javabean除了包括自身的参数还继承了object类的参数class
3、断点调试进入下一个beanclass==java.lang.Class,可以看到class有很多个参数,其中有一个是module参数,(jdk9+的特性可以通过class.module.classloader操作其他类)这个也是绕过的关键。
4、往下继续调试,直到拼接出传递的一个完整的参数,根据最后一个参数进行查找
5、所有的参数都存放在propertyDescriptorCache缓存中,根据directory查找到accessLogValue这个javabean,然后将传递的参数值赋值
6、后续参数的赋值同理,最终经过转换将传递的值赋值给AccessLogValue这个javabean
(对应 conf 目录下的 server.xml 文件)
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="access." suffix=".log"
pattern="%h %l %u %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %{X-Forwarded-For}i "%Dms"" resolveHosts="false"/>
最终在内存中调用AccessLogValue类达到写入shell。
ClassLoader能加载操作到的类
漏洞修复建议:
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(部分)
参考
https://tttang.com/archive/1532/#toc_0x03