作者: 天融信阿尔法实验室
原文链接:https://mp.weixin.qq.com/s/dOycwt_-QpmbuUC8CmxLQQ
一、前言
2020年1月15日,Oracle发布了一系列的安全补丁,其中Oracle WebLogic Server产品有高危漏洞,漏洞编号CVE-2020-2551,CVSS评分9.8分,漏洞利用难度低,可基于IIOP协议执行远程代码。
经过分析这次漏洞主要原因是错误的过滤JtaTransactionManager类,JtaTransactionManager父类AbstractPlatformTransactionManager在之前的补丁里面就加入到黑名单列表了,T3协议使用的是resolveClass方法去过滤的,resolveClass方法是会读取父类的,所以T3协议这样过滤是没问题的。但是IIOP协议这块,虽然也是使用的这个黑名单列表,但不是使用resolveClass方法去判断的,这样默认只会判断本类的类名,而JtaTransactionManager类是不在黑名单列表里面的,它的父类才在黑名单列表里面,这样就可以反序列化JtaTransactionManager类了,而JtaTransactionManager类是存在jndi注入的。
二、漏洞复现
Hashtable<String, String> env = new Hashtable<String, String>();
env.put("java.naming.factory.initial", "weblogic.jndi.WLInitialContextFactory");
env.put("java.naming.provider.url", "iiop://x.x.x.x:7001");
Context context = new InitialContext(env);
JtaTransactionManager...
在本地虚拟机上面就成功执行poc了
三、漏洞分析
根据官网漏洞描述及上面poc中可以知道 主要是利用IIOP协议进行的攻击。在分析之前先了解下这些协议的概念
根据oracle官网的文档
RMI 使用RMI,您可以使用Java编程语言编写分布式程序。RMI易于使用,您不需要学习单独的接口定义语言(IDL),并且可以获得Java固有的“编写一次,随处运行”的好处。客户端,远程接口和服务器完全用Java编写。RMI使用Java远程方法协议(JRMP)进行远程Java对象通信。 RMI缺少与其他语言的互操作性,因为它不使用CORBA-IIOP作为通信协议。
IIOP,CORBA和Java IDL IIOP是CORBA的通信协议,使用TCP / IP作为传输方式。它指定了客户端和服务器通信的标准。CORBA是由对象管理组(OMG)开发的标准分布式对象体系结构。远程对象的接口以平台无关的接口定义语言(IDL)描述。实现了从IDL到特定编程语言的映射,将语言绑定到CORBA / IIOP。 Java SE CORBA / IIOP实现称为Java IDL。与IDlj 编译器一起,Java IDL可用于从Java编程语言定义,实现和访问CORBA对象。
RMI-IIOP 以前,Java程序员不得不在RMI和CORBA / IIOP(Java IDL)之间进行选择,以用于分布式编程解决方案。现在,通过遵守一些限制,RMI服务器对象可以使用IIOP协议并与以任何语言编写的CORBA客户端对象进行通信。此解决方案称为RMI-IIOP。RMI-IIOP将RMI风格的易用性与CORBA跨语言互操作性相结合。
oracle官网的RMI-IIOP的例子 ,为了方便IDEA编辑器DEBUG调试我把官网例子稍改了一下,并把客户端和服务端分开存储。
先创建这三个公用的类
//Message.java
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class Message implements Serializable {
private void readObject(ObjectInputStream s) {
System.out.println("readObject...");
}
private void writeObject(ObjectOutputStream fos) throws IOException {
System.out.println("writeObject...");
}
}
//HelloInterface.java
import java.rmi.Remote;
public interface HelloInterface extends java.rmi.Remote {
public void sayHello(Message from) throws java.rmi.RemoteException;
}
//HelloImpl.java
import javax.rmi.PortableRemoteObject;
public class HelloImpl extends PortableRemoteObject implements HelloInterface {
public HelloImpl() throws java.rmi.RemoteException {
super(); // invoke rmi linking and remote object initialization
}
public void sayHello(Message from) throws java.rmi.RemoteException {
System.out.println("Hello from " + from + "!!");
System.out.flush();
}
}
编写服务端并把上面这三个java文件拷贝到一个java项目里面
//HelloServer.java
import javax.naming.Context;
import javax.naming.InitialContext;
import java.util.Hashtable;
public class HelloServer {
public static void main(String[] args) {
try {
// Step 1: Instantiate the Hello servant
HelloImpl helloRef = new HelloImpl();
// Step 2: Publish the reference in the Naming Service
// using JNDI API
Hashtable<String, String> env = new Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.cosnaming.CNCtxFactory");
env.put(Context.PROVIDER_URL, "iiop://127.0.0.1:1050");
Context initialNamingContext = new InitialContext(env);
initialNamingContext.rebind("HelloService", helloRef);
System.out.println("Hello Server: Ready...");
} catch (Exception e) {
System.out.println("Trouble: " + e);
e.printStackTrace();
}
}
}
直接启动HelloServer的main方法会在target目录编译好代码
在项目的target/classes目录下执行rmic命令生成服务端和客户端的代理类
rmic -iiop org.example.HelloImpl
上面的命令会创建以下文件
_ HelloImpl_Tie.class 用于服务端
_ HelloInterface_Stub.class 用户客户端
启动服务端
# windows系统执行
start orbd -ORBInitialPort 1050
# linux系统执行
orbd -ORBInitialPort 1050 &
用IDEA编辑器直接启动HelloServer的main方法即可启动成功
在创建一个新的客户端项目去启动客户端代码.
import javax.rmi.*;
import java.util.Hashtable;
import javax.naming.InitialContext;
public class HelloClient {
public static void main(String args[]) {
try {
Hashtable<String, String> env = new Hashtable<String, String>();
env.put("java.naming.factory.initial", "com.sun.jndi.cosnaming.CNCtxFactory");
env.put("java.naming.provider.url", "iiop://localhost:1050");
InitialContext ic = new InitialContext(env);
// STEP 1: Get the Object reference from the Name Service
// using JNDI call.
Object objref = ic.lookup("HelloService");
System.out.println("Client: Obtained a ref. to Hello server.");
// STEP 2: Narrow the object reference to the concrete type and
// invoke the method.
HelloInterface hi = (HelloInterface) PortableRemoteObject.narrow(
objref, HelloInterface.class);
Message message = new Message();
hi.sayHello(message);
} catch (Exception e) {
System.err.println("Exception " + e + "Caught");
e.printStackTrace();
}
}
}
启动步骤和服务端步骤除了不执行orbd命令其它步骤都一样.
用wireshark抓包看下
传输的数据中没有aced魔术头,这里不是使用ObjectOutputStream序列化的数据。在客户端和服务端开始位置都DEBUG看下。
先列出客户端的调用栈
writeObject:12, Message
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeObjectWriter:624, IIOPOutputStream (com.sun.corba.se.impl.io)
outputObject:590, IIOPOutputStream (com.sun.corba.se.impl.io)
simpleWriteObject:174, IIOPOutputStream (com.sun.corba.se.impl.io)
writeValueInternal:236, ValueHandlerImpl (com.sun.corba.se.impl.io)
writeValueWithVersion:218, ValueHandlerImpl (com.sun.corba.se.impl.io)
writeValue:150, ValueHandlerImpl (com.sun.corba.se.impl.io)
writeRMIIIOPValueType:807, CDROutputStream_1_0 (com.sun.corba.se.impl.encoding)
write_value:856, CDROutputStream_1_0 (com.sun.corba.se.impl.encoding)
write_value:870, CDROutputStream_1_0 (com.sun.corba.se.impl.encoding)
write_value:665, CDROutputStream_1_0 (com.sun.corba.se.impl.encoding)
write_value:250, CDROutputStream (com.sun.corba.se.impl.encoding)
sayHello:-1, _HelloInterface_Stub
main:26, HelloClient
_HelloInterface_Stub这个class是执行rmic -iiop命令生成的
sayHello方法会从request中获取数据,然后执行CDROutputStream.write_value方法会执行到IIOPOutputStream.invokeObjectWriter方法
这里反射调用Message.writeObject方法,方法参数属性是IIOPOutputStream类型,这也解释了为什么前面抓包数据中没有aced魔术头。
下面是服务端的调用栈
readObject:8, Message
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeObjectReader:1722, IIOPInputStream (com.sun.corba.se.impl.io)
inputObject:1240, IIOPInputStream (com.sun.corba.se.impl.io)
simpleReadObject:416, IIOPInputStream (com.sun.corba.se.impl.io)
readValueInternal:341, ValueHandlerImpl (com.sun.corba.se.impl.io)
readValue:307, ValueHandlerImpl (com.sun.corba.se.impl.io)
read_value:999, CDRInputStream_1_0 (com.sun.corba.se.impl.encoding)
read_value:271, CDRInputStream (com.sun.corba.se.impl.encoding)
_invoke:-1, _HelloImpl_Tie
dispatchToServant:654, CorbaServerRequestDispatcherImpl (com.sun.corba.se.impl.protocol)
dispatch:205, CorbaServerRequestDispatcherImpl (com.sun.corba.se.impl.protocol)
handleRequestRequest:1700, CorbaMessageMediatorImpl (com.sun.corba.se.impl.protocol)
handleRequest:1558, CorbaMessageMediatorImpl (com.sun.corba.se.impl.protocol)
handleInput:940, CorbaMessageMediatorImpl (com.sun.corba.se.impl.protocol)
callback:198, RequestMessage_1_2 (com.sun.corba.se.impl.protocol.giopmsgheaders)
handleRequest:712, CorbaMessageMediatorImpl (com.sun.corba.se.impl.protocol)
dispatch:474, SocketOrChannelConnectionImpl (com.sun.corba.se.impl.transport)
doWork:1237, SocketOrChannelConnectionImpl (com.sun.corba.se.impl.transport)
performWork:490, ThreadPoolImpl$WorkerThread (com.sun.corba.se.impl.orbutil.threadpool)
run:519, ThreadPoolImpl$WorkerThread (com.sun.corba.se.impl.orbutil.threadpool)
服务端也是利用rmic命令生成_HelloImpl_Tie的class
_HelloImpl_Tie有一个_invoke方法,读取流中的数据去执行CDRInputStream.read_value方法。然后会执行IIOPInputStream.invokeObjectReader方法,这个方法会反射执行Message.readObject方法,参数属性也是IIOPInputStream类型。
到这里就可以理解了,RMI-IIOP协议中使用IIOPOutputStream去序列化数据,字节流与ObjectOutputStream序列化数据不太一样。
发送poc看下weblogic调用链.
lookup:417, InitialContext (javax.naming)
doInContext:132, JndiTemplate$1 (com.bea.core.repackaged.springframework.jndi)
execute:88, JndiTemplate (com.bea.core.repackaged.springframework.jndi)
lookup:130, JndiTemplate (com.bea.core.repackaged.springframework.jndi)
lookup:155, JndiTemplate (com.bea.core.repackaged.springframework.jndi)
lookupUserTransaction:565, JtaTransactionManager (com.bea.core.repackaged.springframework.transaction.jta)
initUserTransactionAndTransactionManager:444, JtaTransactionManager (com.bea.core.repackaged.springframework.transaction.jta)
readObject:1198, JtaTransactionManager (com.bea.core.repackaged.springframework.transaction.jta)
invoke:-1, GeneratedMethodAccessor30 (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
readObject:315, ObjectStreamClass (weblogic.utils.io)
readValueData:281, ValueHandlerImpl (weblogic.corba.utils)
readValue:93, ValueHandlerImpl (weblogic.corba.utils)
read_value:2128, IIOPInputStream (weblogic.iiop)
read_value:1936, IIOPInputStream (weblogic.iiop)
read_value_internal:220, AnyImpl (weblogic.corba.idl)
read_value:115, AnyImpl (weblogic.corba.idl)
read_any:1648, IIOPInputStream (weblogic.iiop)
read_any:1641, IIOPInputStream (weblogic.iiop)
_invoke:58, _NamingContextAnyImplBase (weblogic.corba.cos.naming)
invoke:249, CorbaServerRef (weblogic.corba.idl)
invoke:230, ClusterableServerRef (weblogic.rmi.cluster)
run:522, BasicServerRef$1 (weblogic.rmi.internal)
doAs:363, AuthenticatedSubject (weblogic.security.acl.internal)
runAs:146, SecurityManager (weblogic.security.service)
handleRequest:518, BasicServerRef (weblogic.rmi.internal)
run:118, WLSExecuteRequest (weblogic.rmi.internal.wls)
execute:263, ExecuteThread (weblogic.work)
run:221, ExecuteThread (weblogic.work)
断点几个关键的地方
_NamingContextAnyImplBase.invoke方法中请求类型是bind_any,对应var5的值为0,会执行var2.read_any(),var2的值是IIOPInputStream.
会去执行AnyImpl.read_value方法
var2.kind().value()值是29会执行IIOPInputStream.read_value方法。
var14是JtaTransactionManager对象,var13是JtaTransactionManager类的序列化描述符,会执行ValueHandlerImpl.readValue方法
这里执var2是ObjectStreamClass类,var1是JtaTransactionManager对象,var6是JtaTransactionManager对象输入流数据,会执行ObjectStreamClass.readObject方法
这里会反射调用,JtaTransactionManager.readObject方法。
调试到这里就明白了,那看下weblogic怎么修复的。
下载并安装此补丁后,在发送poc会提示。
断点看下这里
可以看到JtaTransactionManager父类AbstractPlatformTransactionManager在黑名单列表里面,第一个断点处verifyClassPermitted方法的for循环会循环判断父类,所以会抛出黑名单异常警告。
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1138/