Spring MVC RCE 分析(一)

Spring MVC RCE 分析(一)

CVE-2010-1622 (未完)

参考:

SpringMVC RCE (CVE-2010-1622) – JohnFrod's Blog

从零开始,分析Spring Framework RCE - 跳跳糖 (tttang.com)

spring rce 从cve-2010-1622到CVE-2022-22965 篇一 - 先知社区 (aliyun.com)

前天尝试入门最新的Spring Beans RCE (CVE-2022-22965) ,后来看各种博文和师兄的复现历程决定先从这个漏洞的前身开始(CVE-2010-1622)。在Spring把CVE-2010-1622洞修上之后,

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类内部的成员变量。

归纳来说:java bean就是

  • 所有的类必须声明为public,这样才能够被外部所访问;
  • 类中所有的属性都必须封装,即:使用private声明;
  • 封装的属性如果需要被外部所操作,则必须编写对应的settergetter方法;
  • 一个JavaBean中至少存在一个无参构造方法

而关于“一个类被当作javaBean使用时,JavaBean的属性是根据方法名推断出来的,它根本看不到java类内部的成员变量。”,这个可以结合下面的“内省”的概念一起理解:

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

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

关于property

此外,通常把一组对应的读方法(getter)和写方法(setter)称为属性(property)。例如,Person类中的name的属性:

  • 对应的读方法是String getName()
  • 对应的写方法是setName(String)

example:

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

Student

public class Student {
    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属性。

demo(没有使用stopClass)

    public static void main(String[] args) throws IntrospectionException
    {

        BeanInfo beanInfo = Introspector.getBeanInfo(Student.class);
        PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
        for(PropertyDescriptor pd:propertyDescriptors){
            System.out.println("Property: "+pd.getName());
        }
    }

image-20220608152807714

使用了stopClass

    public static void main(String[] args) throws IntrospectionException
    {

        BeanInfo beanInfo = Introspector.getBeanInfo(Student.class,Object.class);
        PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
        for(PropertyDescriptor pd:propertyDescriptors){
            System.out.println("Property: "+pd.getName());
        }
    }

image-20220608153403235

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

image-20220608153930550

如果我们接着调用

Introspector.getBeanInfo(Class.class)

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

image-20220608153901514

spring bean

简介

Spring Bean是事物处理组件类和实体类(POJO)对象的总称,是能够被实例化、能够被spring容器管理的java对象。

可以把spring bean理解为java bean的增强版,spring bean是由 Spring IoC 容器管理的,bean 是一个被实例化,组装,并通过 Spring IoC 容器所管理的对象。这些 bean 是由用容器提供的配置元数据创建的,在spring中可以由xml配置文件来创建bean,也就是创建所需要的对象。

假设存在一个配置文件内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://www.springframework.org/schema/beans"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
    <bean id="bean1" class="com.Person">
        <constructor-arg value="panda"/>
        <constructor-arg value="18"/>
    </bean>
</beans>

则Spring相当于调用如下代码:

Bean bean = new com.Person("panda","18");

这看着很简单,但实际上Spring为我们做了很多事情,具体可以借用一张流程图来说明:

image-20220608202358246

SpringMVC如何实现数据绑定

一般一个springboot会包含webmvc框架+tomcat中间件等

首先SpringMVC中当传入一个http请求时会进入DispatcherServlet的doDispatch,然后前端控制器请求HandlerMapping查找Handler,接着HandlerAdapter请求适配器去执行Handler,然后返回ModelAndView,ViewResolver再去解析并返回View,前端解析器去最后渲染视图。

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

image-20220608203135927

过程如上,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"); 
变量覆盖

User类

public class User {
    private String name;

    public String getName() {
        return name;
    }

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

可以看到:其中User的name和UserInfo中id有getset方法

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;
    }

}

可以看到:UserInfo中的user,number和names[]数组只有get方法,id则都有

controller

    @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());
    }

直接访问/test, 结果如下,符合预期

image-20230126124549543

但是如果:

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-20230126135151767

但是我本地复现的时候报错了:java.lang.IllegalArgumentException: Invalid character found in the request target

随后我修改为post传参,又可以复现了,有点迷,参考代码如下

    @RequestMapping(value = "/test", method = RequestMethod.POST)
    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());
    }

image-20230127224551487

如上图所示,这里id由于具有set方法可以被设置,UserInfo其他变量无法被设置(除了User类),所以可见id,number,没有收到传参的影响。但是,names这个字符串数组在没有设置set方法的情况下却成功设置了

调试分析

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

(如果你遇到了搜不到函数的情况,很可能是没能把文件源代码拉下来,一般可以在pom.xml右键选择下载source,也可以参考这个(23条消息) idea无法下载源码 Sources not found for: org.springframework:spring-context:5.1.5.RELEAS_程序员劝退师-TAO的博客-CSDN博客_idea下载spring源码提示)

值得注意的是,我的BeanWrapperImpl是继承自AbstractNestablePropertyAccessor的,setPropertyValue在AbstractNestablePropertyAccessor有实现

最后根据自身情况,我把断点打在AbstractPropertyAccessor.setPropertyValues,debug启动,随后post参数,命中断点

image-20230128105951613

首先在AbstractPropertyAccessor类的setPropertyValues方法传入一个ArrayList,里面包含了请求参数的beans键值对,然后循环取出每一个bean调用

AbstractPropertyAccessor的setPropertyValue

单步进AbstractPropertyAccessor的setPropertyValue后,

image-20230128110200323

可以看到最终还是需要调用BeanWrapperImpl的setPropertyValue,进去看一下

image-20230128110553086

其实是AbstractPropertyAccessor的setPropertyValue, 从可以看出很明显的参数设置的样子

然后我们循环到处理names[0]这轮循环进入BeanWrapperImpl的setPropertyValue,看看在这里是怎么讲没有set方法的字符串数组进行覆盖的

image-20230128110845283

步入setPropertyValue

image-20230128111541081

字符串数组在pv里,追踪一下

进入nestedPa.setPropertyValue

(由于我是post的,和原文略有出入(SpringMVC RCE (CVE-2010-1622) – JohnFrod's Blog))

image-20230128114155266

步入这个

image-20230128114323074

步入这个

image-20230128114339962

仍然会出现getPropertyValue,并且和原文高度一致,步入

image-20230128114806232

几乎是一样了,但是没有看到我们要的那个PropertyDescriptor,步入看看

image-20230128114853484

原来是被封装了,这里看函数意思应该可以理解:所以自此,我们它发现是从CachedIntrospectionResults获取PropertyDescriptor。

如果步入进去看,可以看看它的cachedIntrospectionResults是怎么来的

image-20230128115220556

这里是个属性,那咱们去函数构造方法或者声明成员变量的地方看看

image-20230128115757589

发现它的变量类型既然也是CachedIntrospectionResults

ctrl进去看看

image-20230128115931796

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

咱们跳出来到这个:

image-20230128123158193

继续调看看是如何赋值的。仔细分析

image-20230128123316489

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

最后是调用dobind把它们都绑定起来,感兴趣的可以自己进去看看实现

image-20230128112514899

暂无评论

发送评论 编辑评论


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