Spring Framework CVE-2022-22965漏洞分析

admin 2025年2月16日00:37:04评论8 views字数 9742阅读32分28秒阅读模式
 一.  漏洞利用条件
jdk9+
Spring 及其衍生框架
使用tomcat部署spring项目
使用了POJO参数绑定
Spring Framework 5.3.X < 5.3.18 、5.2.X < 5.2.20 或者其他版本
Spring Framework CVE-2022-22965漏洞分析

二. 漏洞分析

        一开始复现这个漏洞的时候,听其他师傅说是一个老漏洞CVE-2010-1266的绕过,之前也没调试过这个漏洞,看了些分析文章后,大概明白是对Spring中的bean的漏洞利用,通过API Introspector. getBeanInfo 可以获取到POJO的基类Object.class的属性class,进一步可以获取到Class.class的其他属性,其中就包括了classloader,再利用获取到的属性构造利用链,这次爆出来的漏洞既然是绕过,那么原理应该也差不多,首先先搭建环境,构造一个简单的POJO:
public class User {
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
再写个简单的controller:
@RequestMapping("/test")
    public String test(User user){
        System.out.println(user.getName());
        return "hello spring-mvc";
    }
        发送get请求:http://localhost:8080/test?name=test 即可完成一次简单的数据绑定。
        在开始调试分析之前,首先需要对spring的数据绑定体系机构有个简单的了解,其中涉及到一个关键类org.springframework.validation.DataBinder类,DataBinder类实现了TypeConverter和PropertyEditorRegistry接口,作用主要是把字符串形式的参数转换成服务端真正需要的类型的转换,同时还有校验功能,其中有如下这些属性:
@Nullable
private final Object target;//需要数据绑定的对象
private final String objectName;//给对象起得名字默认target
@Nullable
private AbstractPropertyBindingResult bindingResult;//数据绑定后的结果
@Nullable
private SimpleTypeConverter typeConverter;//当target!=null时不会用到
private boolean ignoreUnknownFields = true;//忽略target不存在的属性,作用于PropertyAccessor的setPropertyValues()方法
private boolean ignoreInvalidFields = false;//忽略target不能访问的属性
private boolean autoGrowNestedPaths = true;//当嵌套属性为空时,是否可以实例化该属性
private int autoGrowCollectionLimit = DEFAULT_AUTO_GROW_COLLECTION_LIMIT;//对于集合类型容量的最大值
@Nullable
private String[] allowedFields;//允许数据绑定的资源
@Nullable
private String[] disallowedFields;//不允许的
@Nullable
private String[] requiredFields;//数据绑定必须存在的字段
@Nullable
private ConversionService conversionService;//为getPropertyAccessor().setConversionService(conversionService);
@Nullable
private MessageCodesResolver messageCodesResolver;//同bindingResult的
private BindingErrorProcessor bindingErrorProcessor = new DefaultBindingErrorProcessor();
private final List<Validator> validators = new ArrayList<>();//自定义数据校验器
       其中bindingResult是BeanPropertyBindingResult的实例,内部会持有一个BeanWrapperImpl。
        bind()是数据绑定对象的核心方法:将给定的属性值绑定到此绑定程序的目标,源码如下:
public void bind(PropertyValues pvs) {
        MutablePropertyValues mpvs = pvs instanceof MutablePropertyValues ? (MutablePropertyValues)pvs : newMutablePropertyValues(pvs);
        this.doBind(mpvs);
    }
    protected void doBind(MutablePropertyValues mpvs) {
        this.checkAllowedFields(mpvs);
        this.checkRequiredFields(mpvs);
        this.applyPropertyValues(mpvs);
    }
        再来看看DataBinder类的继承关系,DataBinder有一个子类WebDataBinder,是一个特殊的DataBinder,用于从Web请求参数到JavaBean对象的数据绑定,而WebDataBinder的子类ServletRequestDataBinder用于执行从servlet请求参数到JavaBeans的数据绑定,包括对multipart文件的支持。
Spring Framework CVE-2022-22965漏洞分析
        在普通的Controller实现参数绑定的过程中自动实例化一个ServletRequestDataBinder,在客户端请求的过程中使用当前的ServletRequest作为参数调用bind()方法,于是可以来到这个地方下断点,这个过程中会调用到最上级的DataBinder类的dobind()方法,从而调用到DataBinder的applyPropertyValues方法:
protected void applyPropertyValues(MutablePropertyValues mpvs) {
        try {
            this.getPropertyAccessor().setPropertyValues(mpvs, this.isIgnoreUnknownFields(), this.isIgnoreInvalidFields());
        } catch (PropertyBatchUpdateException var7) {
            PropertyAccessException[] var3 = var7.getPropertyAccessExceptions();
            int var4 = var3.length;

            for(int var5 = 0; var5 < var4; ++var5) {
                PropertyAccessException pae = var3[var5];
                this.getBindingErrorProcessor().processPropertyAccessException(pae, this.getInternalBindingResult());
            }
        }

    }

        applyPropertyValues()方法主要是使用resultBinding对象内的BeanWraperImpl对象完成属性的赋值操作。
Spring Framework CVE-2022-22965漏洞分析
        后续会调用到org.springframework.beans.AbstractNestablePropertyAccessor#getPropertyAccessorForPropertyPath . 
Spring Framework CVE-2022-22965漏洞分析
        跟进AbstractNestablePropertyAccessor#getPropertyAccessorForPropertyPath   
protected AbstractNestablePropertyAccessor getPropertyAccessorForPropertyPath(String propertyPath) {
        int pos = PropertyAccessorUtils.getFirstNestedPropertySeparatorIndex(propertyPath);
        if (pos > -1) {
            String nestedProperty = propertyPath.substring(0, pos);
            String nestedPath = propertyPath.substring(pos + 1);
            AbstractNestablePropertyAccessor nestedPa = this.getNestedPropertyAccessor(nestedProperty);
            return nestedPa.getPropertyAccessorForPropertyPath(nestedPath);
        } else {
            return this;
        }
    }
        这里如果传进来的propertyPath包含.符号,pos则会赋值大于-1,具体的逻辑就不跟了,进入if语句后调用getNestedPropertyAccessor方法,之后经过如下的调用栈,来到resultBinding对象内的BeanWraperImpl对象的getCachedIntrospectionResults方法。
Spring Framework CVE-2022-22965漏洞分析
private CachedIntrospectionResults getCachedIntrospectionResults() {
        if (this.cachedIntrospectionResults == null) {
            this.cachedIntrospectionResults = CachedIntrospectionResults.forClass(this.getWrappedClass());
        }

        return this.cachedIntrospectionResults;
    }

        CachedIntrospectionResults 缓存了所有的bean中属性的信息,通过调试最后return的cachedIntrospectionResults变量可以看到,能够获取到的PropertyDescriptor属性描述器不仅仅有name,还有关键的class属性。
Spring Framework CVE-2022-22965漏洞分析
        也可以在本地新建一个测试类来获取user这个bean的属性,如下:
Spring Framework CVE-2022-22965漏洞分析
        至此,我们还需要了解到怎么去绕过CachedIntrospectionResults中的黑名单,看到CachedIntrospectionResults的构造方法。
Spring Framework CVE-2022-22965漏洞分析
for(int var5 = 0; var5 < var4; ++var5) {
                PropertyDescriptor pd = var3[var5];
                if (Class.class != beanClass || !"classLoader".equals(pd.getName()) && !"protectionDomain".equals(pd.getName())) {
                    if (logger.isTraceEnabled()) {
                        logger.trace("Found bean property '" + pd.getName() + "'" + (pd.getPropertyType() != null ? " of type [" + pd.getPropertyType().getName() + "]" : "") + (pd.getPropertyEditorClass() != null ? "; editor [" + pd.getPropertyEditorClass().getName() + "]" : ""));
                    }

                    pd = this.buildGenericTypeAwarePropertyDescriptor(beanClass, pd);
                    this.propertyDescriptorCache.put(pd.getName(), pd);
                }
            }

        在第一次获取Bean的属性信息过程中,会初始化CachedIntrospectionResults从而去调用到其构造方法,但其中有个classLoader和protectionDomain的黑名单,导致于在所有jdk版本下面都不能直接去通过class属性中的classloader进行漏洞利用,所以到这里即便能够操作从bean中获得的动态class,也无法进行进一步利用。
        绕过方法就是利用jdk9+的新特性,也就是module机制,简称模块化系统,在jdk9+中Class类有一个名为getModule()的新方法,它返回该类作为其成员的模块引用,而包含的模块引用当中就有classloader,如下:
Spring Framework CVE-2022-22965漏洞分析
        于是可以通过class中的module去间接获取classloader,使CachedIntrospectionResults初始化时的黑名单无效化。
        后面的利用思路,就是去思考能利用哪些可控的属性去完成漏洞利用,首先去枚举都有哪些属性,这里贴个小脚本:
<%!
    public void processClass(Object instance, javax.servlet.jsp.JspWriter out, java.util.HashSet set, String poc){
        try {
            Class<?> c = instance.getClass();
            set.add(instance);
            Method[] allMethods = c.getMethods();
            for (Method m : allMethods) {
                if (!m.getName().startsWith("set")) {
                    continue;
                }
                if (!m.toGenericString().startsWith("public")) {
                    continue;
                }
                Class<?>[] pType  = m.getParameterTypes();
                if(pType.length!=1) continue;

                if(pType[0].getName().equals("java.lang.String")||
                        pType[0].getName().equals("boolean")||
                        pType[0].getName().equals("int")){
                    String fieldName = m.getName().substring(3,4).toLowerCase()+m.getName().substring(4);
                    out.print(poc+"."+fieldName + "<br>");
                }
            }
            for (Method m : allMethods) {
                if (!m.getName().startsWith("get")) {
                    continue;
                }
                if (!m.toGenericString().startsWith("public")) {
                    continue;
                }
                Class<?>[] pType  = m.getParameterTypes();
                if(pType.length!=0) continue;
                if(m.getReturnType() == Void.TYPE) continue;
                Object o = m.invoke(instance);
                if(o!=null)
                {
                    if(set.contains(o)) continue;
                    processClass(o,out, set, poc+"."+m.getName().substring(3,4).toLowerCase()+m.getName().substring(4));
                }
            }
        } catch (java.io.IOException x) {
            x.printStackTrace();
        } catch (java.lang.IllegalAccessException x) {
            x.printStackTrace();
        } catch (java.lang.reflect.InvocationTargetException x) {
            x.printStackTrace();
        }
    }
%>
<%
    java.util.HashSet set = new java.util.HashSet<Object>();
    String poc = "class.module.classLoader";
    User user = new User();
    processClass(user.getClass().getModule().getClassLoader(),out,set,poc);
%>

        这段脚本只获取了int、string与boolean这些基本类型参数的属性,访问得到如下:
Spring Framework CVE-2022-22965漏洞分析
        枚举出来大概有两百八多个属性,在这些属性当中有几个控制着在tomcat上生成的access log的文件名,其默认值如下:
class.module.classLoader.resources.context.parent.pipeline.first.directory =logs
//将放置由此阀创建的日志文件的目录的绝对路径名或相对路径名。
class.module.classLoader.resources.context.parent.pipeline.first.prefix =localhost_access_log
//前缀添加到每个日志文件名称的开头
class.module.classLoader.resources.context.parent.pipeline.first.suffix = .txt
//后缀添加到每个日志文件名称的末尾
class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat =.yyyy-mm-dd
//日志文件名中的自定义日期格式
class.module.classLoader.resources.context.parent.pipeline.first.pattern
//一种格式化布局,用于标识要记录的请求和响应中的各种信息字段,或单词 common或combined选择标准格式
        其中比较值得注意的是其中的pattern,在tomcat中其属性的值由文字文本字符串组成,与前缀为“%”字符的模式标识符组合,还支持从cookie,传入头,传出响应头,Session或ServletRequest中的其他内容中写入信息,有如下模型:
%{xxx}i 传入请求头
%{xxx}o 用于传出响应头
%{xxx}c 对于特定的请求cookie
%{xxx}r xxx是ServletRequest中的一个属性
%{xxx}s xxx是HttpSession中的一个属性
      然后通过调试也可以观察出各属性默认值:
Spring Framework CVE-2022-22965漏洞分析
        于是就可以构造请求包,将log日志文件后缀改为.jsp,在请求头加入标识符变量值在pattern中构造webshell内容,发送完payload后重新调试可发现已成功修改日志配置。
Spring Framework CVE-2022-22965漏洞分析
        在实际生产环境中,如果是tomcat直接单独启动的话,可以直接控制写入相对路径为“./webapps/ROOT/”下即可正常访问webshell。
Spring Framework CVE-2022-22965漏洞分析
Spring Framework CVE-2022-22965漏洞分析

三. 修复方式

目前官方已经发布了补丁,在最新版本v5.3.18和v5.2.20中已经完成了修复,如下:
https://github.com/spring-projects/spring-framework/commit/002546b3e4b8d791ea6acccb81eb3168f51abb15
Spring Framework CVE-2022-22965漏洞分析
修复过后class类中缓存的属性只包含以下这几个:
Spring Framework CVE-2022-22965漏洞分析
Spring Framework CVE-2022-22965漏洞分析

四. Reference

https://blog.csdn.net/god_7z1/article/details/24416717
https://blog.csdn.net/lixiangchibang/article/details/84024253
Spring Framework CVE-2022-22965漏洞分析

原文始发于微信公众号(山石网科安全技术研究院):Spring Framework CVE-2022-22965漏洞分析

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年2月16日00:37:04
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Spring Framework CVE-2022-22965漏洞分析https://cn-sec.com/archives/905832.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息