前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring-Core RCE分析

Spring-Core RCE分析

作者头像
JDArmy
发布2022-06-06 09:16:19
5100
发布2022-06-06 09:16:19
举报
文章被收录于专栏:JDArmyJDArmy

迟来的SpringMVC 框架RCE分析。本文章简单介绍了SpringMVC框架请求处理流程,并以此对漏洞进行了分析与复现。

框架浅析

SpringMVC其本质上是一个Servlet,它的请求处理主要是在DispatcherServlet中,这里大概有四步:

  1. 1. 根据Request找到Handler
  2. 2. 根据Handler找到HandlerAdapter
  3. 3. 用HandlerAdapter调用Handler处理请求
  4. 4. 处理结果并渲染输出给用户

借用一张图来看下这个流程

Handler是用来处理请求,SpringMVC内置了大量的Handler,我们重点关注下其中对参数进行处理的,主要是HandlerMethodArgumentResolverHandlerMethodReturnValueHandler,前者表示一个参数解析器,后者除了解析参数之外还可以处理相应类型的返回值。以下是HandlerMethodArgumentResolver的实现类

它们基本上都实现了:

代码语言:javascript
复制
public boolean supportsParameter(MethodParameter parameter) //和 
public Object  resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory)

RequestMapping对应参数符合supportsParameter会使用resolveArgument解析请求,并最终得到参数的值传入RequestMapping,这里以RequestParamMapMethodArgumentResolver简单介绍下:

代码语言:javascript
复制
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
        return (requestParam != null && Map.class.isAssignableFrom(parameter.getParameterType()) &&
                !StringUtils.hasText(requestParam.name()));
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
        
        ......
      else {
                Map<String, String[]> parameterMap = webRequest.getParameterMap();
                Map<St    ring, String> result = CollectionUtils.newLinkedHashMap(parameterMap.size());
                parameterMap.forEach((key, values) -> {
                    if (values.length > 0) {
                        result.put(key, values[0]);
                    }
                });
                return result;
            }
    }

首先看其支持类型,需要有RequestParam注解,且参数类型为Map,所以可以定义如下接口:

代码语言:javascript
复制
    @ResponseBody
    @RequestMapping("/mvc/world")
    public String world(@RequestParam HashMap<String, String> map) {
        return "successfuladd";
    }

该接口就会被RequestParamMapMethodArgumentResolver处理,很容易看出这里简单的做了个类型转换,这里的result就是我们需要的参数了。

有趣的是这里如果两个相同参数的请求,其只会取第一个的值,而如果是RequestParamMethodArgumentResolver进行处理时会把两个参数值通过,进行连接。

部分解析器及其作用:

漏洞分析

前面扯了那么多,现在终于是进入正题了,先来搭建下漏洞环境:

  • • JDK:11.0.14
  • • Tomcat:9.0.60
  • • Spring 5.3.17

主要代码如下:

代码语言:javascript
复制
@Controller
public class TestController {

    @ResponseBody
    @RequestMapping("/mvc/hello")
    public String hello(User user) {
        System.out.println(user.getName());
        return "success";
    }
}

//User
public class User {
  private String name;

  public String getName() {
    return name;
  }

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

PoC:class.module.classLoader.resources.context.parent.pipeline.first.pattern=***

单独看PoC可能会疑惑这个参数是怎么来的,所以这里要结合着环境进行分析。可以看到hello的参数User,这是一个没有注释的非通用类型参数,而上文中有提到不同参数类型的解析器也不一样,现在的情况会由ModelAttributeMethodProcessor进行处理,跟进其resolveArgument方法,它会尝试从当前请求中获取值并绑定到user上。

一路跟进bindRequestParameters函数直到org.springframework.validation#applyPropertyValues

这里经过getPropertyAccessor()我们实际上获取到了一个User对象的BeanWrapper实例。

在这里我们补充下BeanWrapper相关的内容,在Spring中,BeanWrapper接口是对Bean的包装,定义了对包装对象的属性值的访问与修改的接口,BeanWrapperImpl则是对BeanWrapper的默认实现,BeanWrapperImpl类有多个设置bean属性值的重载方法,其中就有public void setPropertyValue(PropertyValue pv)PropertyValue 以对象的方式存储键值对,比Map使用起来要灵活,通过BeanWrapperImpl设置属性值:

代码语言:javascript
复制
public class BeanWrapperTest {
    public static void main(String[] args) {
        User user=new User();

        BeanWrapper bw= PropertyAccessorFactory.forBeanPropertyAccess(user);
        bw.setPropertyValue(new PropertyValue("name","bean"));

        System.out.println(user.getName());
    }
}

也可以通过getPropertyDescriptors获取所有属性值:

代码语言:javascript
复制
public class BeanWrapperTest {
    public static void main(String[] args) {
        User user=new User();

        BeanWrapper bw= PropertyAccessorFactory.forBeanPropertyAccess(user);
        for (PropertyDescriptor p :
            bw.getPropertyDescriptors()) {
            System.out.println(p.getName());
        }
    }
}

//output
class
name

可以看到除了除了name之外还会有一个class,那这是不是说明class也可以被我们修改呢?看一下setPropertyValue的代码,它会进入getPropertyAccessorForPropertyPath,它支持两种方式的属性值,一种是直接用name进行操作,一种则是user.name的形式进行递归逐步获取到user后对name进行操作,这里对第二种情况进行分析:

添加新类God:

代码语言:javascript
复制
public class God {
    private String name;

    public String getName(){
        return name;
    }

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

同时在User中加入:

代码语言:javascript
复制
    private God god = new God();


    public God getGod(){
        return god;
    }

最后运行

代码语言:javascript
复制
public class BeanWrapperTest {
    public static void main(String[] args) {
        User user = new User();

        BeanWrapper bw= PropertyAccessorFactory.forBeanPropertyAccess(user);
        bw.setPropertyValue(new PropertyValue("god.name","bean"));
        System.out.println(user.getGod().getName());
    }
}

第一次解析god,如果之前未解析过bean类,首先会对该类进行分析并缓存,使用的方法是CachedIntrospectionResults.forClass,在获取到所有get,set方法后循环判断了该类为Class的同时属性是不是classLoader,防止了直接class.classLoader来进一步获取值

缓存之后就开始获取属性值了,如果该属性可读的话就会在getValue时执行其get方法,这里的Value就是God实例。

最后会以该实例生成一个新的nestedPa返回并进入第二次循环。

不过第二次时已经没有.了,所以直接返回this,也就是god,并以此知道要设置的值为god.name,所以后续就进入了设置属性值的流程,只有当该属性值存在且可写的情况下才可以继续往下执行。

至此整个流程就结束了,让我们回到漏洞

setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields())其实也调用了setPropertyValue(PropertyValue pv)

那么结合上文对setPropertyValue流程的分析,其实我们已经大致理解了payload的格式,包括为什么用class.module.classLoader而不是直接class.classLoader。在Tomcat中是ParallelWebappClassLoader,而且其有一个属性getResources,就这样层层递归,最终操作日志,达成任意文件写入,从而实现RCE,在SpringBootLaunchedURLClassLoader中并不存在getResources所以直接使用SpringBoot的情况下上述Payload是不起作用的。

修复方案

针对该漏洞Spring以及 Tomcat都做出了修复。

Spring: Class类仅可以获取name相关的值了,而且对没有写操作权限的ClassLoader以及ProtectionDomain做了限制。

Tomcat则是直接把getResources返回为空了。

参考文章

  • • https://paper.seebug.org/1877/
  • • https://blog.csdn.net/zhiweianran/article/details/7919129
  • • http://rui0.cn/archives/1158
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-04-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 JDArmy 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 框架浅析
  • 漏洞分析
  • 修复方案
  • 参考文章
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档