草庐IT

Spring Framework CVE-2022-22965漏洞详细分析

H_00c8 2023-03-28 原文

一. 漏洞利用条件

jdk9+

Spring 及其衍生框架

使用tomcat部署spring项目

使用了POJO参数绑定

Spring Framework 5.3.X < 5.3.18 、2.X < 5.2.20 或者其他版本

二. 漏洞分析

一开始复现这个漏洞的时候,听其他师傅说是一个老漏洞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 即可完成一次简单的数据绑定。

【相关技术资料】
1、网络安全学习路线
2、电子书籍(白帽子)
3、安全大厂内部视频
4、100份src文档
5、常见安全面试题
6、ctf大赛经典题目解析
7、全套工具包
8、应急响应笔记

在开始调试分析之前,首先需要对spring的数据绑定体系机构有个简单的了解,其中涉及到一个关键类org.springframework.validation.DataBinder类,DataBinder类实现了TypeConverter和PropertyEditorRegistry接口,作用主要是把字符串形式的参数转换成服务端真正需要的类型的转换,同时还有校验功能,其中有如下这些属性:

@Nullableprivate final Object target;//需要数据绑定的对象private final String objectName;//给对象起得名字默认target@Nullableprivate AbstractPropertyBindingResult bindingResult;//数据绑定后的结果@Nullableprivate 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;//对于集合类型容量的最大值@Nullableprivate String[] allowedFields;//允许数据绑定的资源@Nullableprivate String[] disallowedFields;//不允许的@Nullableprivate String[] requiredFields;//数据绑定必须存在的字段@Nullableprivate ConversionService conversionService;//为getPropertyAccessor().setConversionService(conversionService);@Nullableprivate MessageCodesResolver messageCodesResolver;//同bindingResult的private BindingErrorProcessor bindingErrorProcessor = new DefaultBindingErrorProcessor();private final List<Validator> validators = new ArrayList<>();//自定义数据校验器

其中bindingResult是BeanPropertyBindingResult的实例,内部会持有一个BeanWrapperImpl。
bind()是数据绑定对象的核心方法:将给定的属性值绑定到此绑定程序的目标,源码如下:

publicvoid bind(PropertyValues pvs) { MutablePropertyValues mpvs = pvs instanceofMutablePropertyValues ? (MutablePropertyValues)pvs : newMutablePropertyValues(pvs); this.doBind(mpvs); } protectedvoid doBind(MutablePropertyValues mpvs) { this.checkAllowedFields(mpvs); this.checkRequiredFields(mpvs); this.applyPropertyValues(mpvs); }

再来看看DataBinder类的继承关系,DataBinder有一个子类WebDataBinder,是一个特殊的DataBinder,用于从Web请求参数到JavaBean对象的数据绑定,而WebDataBinder的子类ServletRequestDataBinder用于执行从servlet请求参数到JavaBeans的数据绑定,包括对multipart文件的支持。

image.png

在普通的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对象完成属性的赋值操作。

image.png

后续会调用到。

org.springframework.beans.AbstractNestablePropertyAccessor#getPropertyAccessorForPropertyPath

跟进AbstractNestablePropertyAccessor#getPropertyAccessorForPropertyPath。

protectedAbstractNestablePropertyAccessor 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); returnnestedPa.getPropertyAccessorForPropertyPath(nestedPath); } else{ returnthis; } }

这里如果传进来的propertyPath包含.符号,pos则会赋值大于-1,具体的逻辑就不跟了,进入if语句后调用getNestedPropertyAccessor方法,之后经过如下的调用栈,来到resultBinding对象内的BeanWraperImpl对象的getCachedIntrospectionResults方法。

image.png
private CachedIntrospectionResults getCachedIntrospectionResults() { if (this.cachedIntrospectionResults == null) { this.cachedIntrospectionResults = CachedIntrospectionResults.forClass(this.getWrappedClass()); } return this.cachedIntrospectionResults; }

CachedIntrospectionResults 缓存了所有的bean中属性的信息,通过调试最后return的cachedIntrospectionResults变量可以看到,能够获取到的PropertyDescriptor属性描述器不仅仅有name,还有关键的class属性。

image.png

也可以在本地新建一个测试类来获取user这个bean的属性,如下:

image.png

至此,我们还需要了解到怎么去绕过CachedIntrospectionResults中的黑名单,看到CachedIntrospectionResults的构造方法。

image.png

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,如下:

image.png


于是可以通过class中的module去间接获取classloader,使CachedIntrospectionResults初始化时的黑名单无效化。

后面的利用思路,就是去思考能利用哪些可控的属性去完成漏洞利用,首先去枚举都有哪些属性,这里贴个小脚本:

<%! publicvoid 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 + "
"); } } 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 = newjava.util.HashSet<Object>(); String poc = "class.module.classLoader"; User user = newUser(); processClass(user.getClass().getModule().getClassLoader(),out,set,poc); %>

这段脚本只获取了int、string与boolean这些基本类型参数的属性,访问得到如下:

image.png

枚举出来大概有两百八多个属性,在这些属性当中有几个控制着在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中的一个属性

然后通过调试也可以观察出各属性默认值:

image.png

于是就可以构造请求包,将log日志文件后缀改为.jsp,在请求头加入标识符变量值在pattern中构造webshell内容,发送完payload后重新调试可发现已成功修改日志配置。

image.png

在实际生产环境中,如果是tomcat直接单独启动的话,可以直接控制写入相对路径为“./webapps/ROOT/”下即可正常访问webshell。

image.png

三. 修复方式

目前官方已经发布了补丁,在最新版本v5.3.18和v5.2.20中已经完成了修复,如下:

image.png

修复过后class类中缓存的属性只包含以下这几个:

image.png

有关Spring Framework CVE-2022-22965漏洞详细分析的更多相关文章

  1. Tomcat AJP 文件包含漏洞(CVE-2020-1938) - 2

    目录1.漏洞简介2、AJP13协议介绍Tomcat主要有两大功能:3.Tomcat远程文件包含漏洞分析4.漏洞复现 5、漏洞分析6.RCE实现的原理1.漏洞简介2020年2月20日,公开CNVD的漏洞公告中发现ApacheTomcat文件包含漏洞(CVE-2020-1938)。ApacheTomcat是Apache开源组织开发的用于处理HTTP服务的项目。ApacheTomcat服务器中被发现存在文件包含漏洞,攻击者可利用该漏洞读取或包含Tomcat上所有webapp目录下的任意文件。该漏洞是一个单独的文件包含漏洞,依赖于Tomcat的AJP(定向包协议)。AJP自身存在一定缺陷,导致存在可控

  2. 在VMware16虚拟机安装Ubuntu详细教程 - 2

    在VMware16.2.4安装Ubuntu一、安装VMware1.打开VMwareWorkstationPro官网,点击即可进入。2.进入后向下滑动找到Workstation16ProforWindows,点击立即下载。3.下载完成,文件大小615MB,如下图:4.鼠标右击,以管理员身份运行。5.点击下一步6.勾选条款,点击下一步7.先勾选,再点击下一步8.去掉勾选,点击下一步9.点击下一步10.点击安装11.点击许可证12.在百度上搜索VM16许可证,复制填入,然后点击输入即可,亲测有效。13.点击完成14.重启系统,点击是15.双击VMwareWorkstationPro图标,进入虚拟机主

  3. 映宇宙2022年营收63亿元:同比下降三成,毛利率提升4.3个百分点 - 2

    3月26日,映宇宙(HK:03700,即“映客”)发布截至2022年12月31日的2022年度业绩财务报告。财报显示,映宇宙2022年的总营收为63.19亿元,较2021年同期的91.76亿元下降31.1%。2022年,映宇宙的经营亏损为4698.7万元,2021年同期则为净利润4.57亿元;期内亏损(净亏损)为1.68亿元,2021年同期的净利润为4.33亿元;非国际财务报告准则经调整净利润为3.88亿元,2021年同期为4.82亿元,同比下降19.6%。 映宇宙在财报中表示,收入减少主要是由于行业竞争加剧,该集团对旗下产品采取更为谨慎的运营策略以应对市场变化。不过,映宇宙的毛利率则有所提升

  4. 100个python算法超详细讲解:画直线 - 2

    1.问题描述使用Python的turtle(海龟绘图)模块提供的函数绘制直线。2.问题分析一幅复杂的图形通常都可以由点、直线、三角形、矩形、平行四边形、圆、椭圆和圆弧等基本图形组成。其中的三角形、矩形、平行四边形又可以由直线组成,而直线又是由两个点确定的。我们使用Python的turtle模块所提供的函数来绘制直线。在使用之前我们先介绍一下turtle模块的相关知识点。turtle模块提供面向对象和面向过程两种形式的海龟绘图基本组件。面向对象的接口类如下:1)TurtleScreen类:定义图形窗口作为绘图海龟的运动场。它的构造器需要一个tkinter.Canvas或ScrolledCanva

  5. 建模分析 | 平面2R机器人(二连杆)运动学与动力学建模(附Matlab仿真) - 2

    目录0专栏介绍1平面2R机器人概述2运动学建模2.1正运动学模型2.2逆运动学模型2.3机器人运动学仿真3动力学建模3.1计算动能3.2势能计算与动力学方程3.3动力学仿真0专栏介绍?附C++/Python/Matlab全套代码?课程设计、毕业设计、创新竞赛必备!详细介绍全局规划(图搜索、采样法、智能算法等);局部规划(DWA、APF等);曲线优化(贝塞尔曲线、B样条曲线等)。?详情:图解自动驾驶中的运动规划(MotionPlanning),附几十种规划算法1平面2R机器人概述如图1所示为本文的研究本体——平面2R机器人。对参数进行如下定义:机器人广义坐标

  6. 网站日志分析软件--让网站日志分析工作变得更简单 - 2

    网站的日志分析,是seo优化不可忽视的一门功课,但网站越大,每天产生的日志就越大,大站一天都可以产生几个G的网站日志,如果光靠肉眼去分析,那可能看到猴年马月都看不完,因此借助网站日志分析工具去分析网站日志,那将会使网站日志分析工作变得更简单。下面推荐两款网站日志分析软件。第一款:逆火网站日志分析器逆火网站日志分析器是一款功能全面的网站服务器日志分析软件。通过分析网站的日志文件,不仅能够精准的知道网站的访问量、网站的访问来源,网站的广告点击,访客的地区统计,搜索引擎关键字查询等,还能够一次性分析多个网站的日志文件,让你轻松管理网站。逆火网站日志分析器下载地址:https://pan.baidu.

  7. 什么是0day漏洞?如何预防0day攻击? - 2

    什么是0day漏洞?0day漏洞,是指已经被发现,但是还未被公开,同时官方还没有相关补丁的漏洞;通俗的讲,就是除了黑客,没人知道他的存在,其往往具有很大的突发性、破坏性、致命性。0day漏洞之所以称为0day,正是因为其补丁永远晚于攻击。所以攻击者利用0day漏洞攻击的成功率极高,往往可以达到目的并全身而退,而防守方却一无所知,只有在漏洞公布之后,才后知后觉,却为时已晚。“后知后觉、反应迟钝”就是当前安全防护面对0day攻击的真实写照!为了方便大家理解,中科三方为大家梳理当前安全防护模式下,一个漏洞从发现到解决的三个时间节点:T0:此时漏洞即0day漏洞,是已经被发现,还未被公开,官方还没有相

  8. ABB-IRB-1200运动学分析MATLAB RVC工具分析+Simulink-Adams联合仿真 - 2

    一、机器人介绍        此处是基于MATLABRVC工具箱,对ABB-IRB-1200型号的微型机械臂进行正逆向运动学分析,并利Simulink工具实现对机械臂进行具有动力学参数的末端轨迹规划仿真,最后根据机械模型设计Simulink-Adams联合仿真。 图1.ABBIRB 1200尺寸参数示意图ABBIRB 1200提供的两种型号广泛适用于各作业,且两者间零部件通用,两种型号的工作范围分别为700 mm 和 900 mm,大有效负载分别为 7 kg 和5 kg。 IRB 1200 能够在狭小空间内能发挥其工作范围与性能优势,具有全新的设计、小型化的体积、高效的性能、易于集成、便捷的接

  9. 关于Qt程序打包后运行库依赖的常见问题分析及解决方法 - 2

    目录一.大致如下常见问题:(1)找不到程序所依赖的Qt库version`Qt_5'notfound(requiredby(2)CouldnotLoadtheQtplatformplugin"xcb"in""eventhoughitwasfound(3)打包到在不同的linux系统下,或者打包到高版本的相同系统下,运行程序时,直接提示段错误即segmentationfault,或者Illegalinstruction(coredumped)非法指令(4)ldd应用程序或者库,查看运行所依赖的库时,直接报段错误二.问题逐个分析,得出解决方法:(1)找不到程序所依赖的Qt库version`Qt_5'

  10. H2数据库配置及相关使用方式一站式介绍(极为详细并整理官方文档) - 2

    目录H2数据库入门以及实际开发时的使用1.H2数据库的初识1.1H2数据库介绍1.2为什么要使用嵌入式数据库?1.3嵌入式数据库对比1.3.1性能对比1.4技术选型思考2.H2数据库实战2.1H2数据库下载搭建以及部署2.1.1H2数据库的下载2.1.2数据库启动2.1.2.1windows系统可以在bin目录下执行h2.bat2.1.2.2同理可以通过cmd直接使用命令进行启动:2.1.2.3启动后控制台页面:2.1.3spring整合H2数据库2.1.3.1引入依赖文件2.1.4数据库通过file模式实际保存数据的位置2.2H2数据库操作2.2.1Mysql兼容模式2.2.2Mysql模式

随机推荐