作者: 天融信阿爾法實驗室
原文鏈接: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 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1138/
暫無評論