去年weblogic出白名單時研究了下怎么繞過,總結出了下面的思路,本想再找找有無新的攻擊面的思路,但是找了幾次都沒找到,后來就擱置了。昨天看見https://xz.aliyun.com/t/11037這篇文章,提到了T3協議繞過,才想起自己也搞過這塊的研究,遂將研究的內容分享出來拋磚引玉,希望能看到大佬們更多的分析文章。

T3協議交互流程

0x01 協商

Weblogic處理T3基礎信息協商的類如下

weblogic.rjvm.t3.MuxableSocketT3#readIncomingConnectionBootstrapMessage

客戶端發送:

t3 10.3.6
AS:255
HL:19

服務端發送:

HELO:12.2.1.4.false
AS:2048
HL:19
MS:10000000
PN:DOMAIN

客戶端發送的都是這種鍵值對的形式,存在以下可用鍵:

其中,AS和HL是兩種常用的頭信息,這里和我們利用的關系不大,就不分析了,需要注意的時候AS需要設置成01,盡可能小。

0x02 信息發送

信息處理的主要代碼在weblogic.rjvm.MsgAbbrevInputStream#init函數

super.init(data, 4);#變量初始化,以及跳過一個int(數據總長度)
this.connection = connection;
this.responseId = -1;
this.user = null;
this.setValidatingClass(false);
this.header.readHeader(this, connection.getRemoteHeaderLength());#讀取header信息
if (this.connectionManager.thisRJVM != null) {
    this.header.src = this.connectionManager.thisRJVM.getID();
}
this.header.dest = JVMID.localID();
if (this.requiresUnauthenticatedFilter()) {
    WebLogicObjectInputFilter.setUnauthenticatedFilterForStream(this.objectStream);
    this.objectStream.setFilterType(MsgAbbrevInputStream.FilterType.UNAUTHENTICATED);
} else if (this.objectStream.getFilterType() == null) {
    WebLogicObjectInputFilter.setWebLogicFilterForStream(this.objectStream);
    this.objectStream.setFilterType(MsgAbbrevInputStream.FilterType.WLS);
}
if (KernelStatus.DEBUG && debugMessaging.isDebugEnabled()) {
}
this.mark(this.header.abbrevOffset);
this.skip((long)(this.header.abbrevOffset - this.pos()));
connection.readMsgAbbrevs(this);
this.reset();
if (JVMID.localID().equals(this.header.dest)) {
    if (!this.header.getFlag(8)) {
        this.read81Contexts();
    } else {
        this.readExtendedContexts();
    }
}

第一步super.init()會進行一些初始化,并跳過前面的4個byte的長度信息,首先讀取的就是header信息,處理函數如下weblogic.rjvm.JVMMessage#readHeader

try {
    this.cmd = JVMMessage.Command.getByValue(is.readByte());
    this.QOS = is.readByte();
    this.flags = is.readByte() & 255;
    this.hasJVMIDs = this.getFlag(1);
    this.hasTX = this.getFlag(2);
    this.hasTrace = this.getFlag(4);
    this.responseId = is.readInt();
    this.invokableId = is.readInt();
    this.abbrevOffset = is.readInt();
    int skip = remoteHeaderLen - 19;
    if (skip > 0) {
        is.skip((long)skip);
    }
} catch (IOException var4) {
    throw new AssertionError("Exception reading message header", var4);
}

總的一共是19個byte

這里說一些比較重要的

  • cmd,代表的是執行指令,這個值會影響代碼進入不同的處理分支
  • flags,一個標志位,標識數據包中的信息種類,同樣會影響代碼進入不同分支
  • abbreOffset,一個int變量,標識header長度,在后面對流的控制會用到。

在讀取完header后,會調用mark和skip函數,標記當前位置,并跳過一部分內容,跳過的長度為abbrevOffset 的值-當前讀取的長度,然后調用connection.readMsgAbbrevs(this);讀取信息。

this.skip((long)(this.header.abbrevOffset - this.pos()));

readMsgAbbrevs函數就會對流中的序列化數據進行反序列化,調用的是InboundMsgAbbrev類的readObject方法,并存儲在棧中。調用棧如下

會調用到FilteringObjectInputStream的resolveClass函數。這里也就是之前weblogic的漏洞會觸發的readObject的地方。但是在21年4月的補丁中,Weblogic使用了白名單,只有以下七種類可以被反序列化,因此所有Weblogic原本的漏洞都無法使用。

  • java.lang.String
  • weblogic.rmi.spi.ServiceContext
  • weblogic.rjvm.ClassTableEntry
  • weblogic.rjvm.JVMID
  • weblogic.security.acl.internal.AuthenticatedUser
  • weblogic.rmi.extensions.server.RuntimeMethodDescriptor
  • weblogic.utils.io.Immutable

讀取結束后,執行reset()方法,流的指針回到之前mark處,根據header中flag值的不同,進入不同的分支。

if (!this.header.getFlag(8)) {
    this.read81Contexts();
} else {
    this.readExtendedContexts();
}

根據需求設定flag值,可以進入如下函數

該函數的關鍵代碼如下:

if (b == 4) {
  ObjectStreamClass desc = this.readClassDescriptor();
  Class cl = this.resolveClass(desc);
  weblogic.utils.io.ObjectStreamClass osc = weblogic.utils.io.ObjectStreamClass.lookup(cl);
  Externalizable e = (Externalizable)osc.newInstance();
  int envelopeLength = this.readInt();
  int startEnvelope = this.pos();
  this.pushExternalizableInfo(startEnvelope + envelopeLength, cl.getName());
  boolean var12 = false;
  try {
      var12 = true;
      e.readExternal(this);
      var12 = false;
  }

這部分代碼就是T3白名單繞過的關鍵部分了。

白名單繞過

上面的函數中實例化了一個實現了Externalizable接口的類,并調用了它的readExternal。這個類的Desc信息來源于this.readClassDescriptor();這個函數會從棧中的ClassTableEntry中讀取descriptor屬性作為desc,接著調用resovleClass方法生成類,這里調用的是MsgAbbrevInputStream的resolveClass方法,只存在黑名單判斷,不存在白名單。接著會實例化這個類,并調用readExternal方法。棧中的ClassTableEntry類是在Weblogic T3的白名單中的,因此可以順利被傳入。

在后面在readExternal中需要注意,要想真正繞過白名單,不能在Externalizable 實例的readExternal里調用原生的readObject方法,不然還是會受黑名單影響。

舉例,在傳入的ClassTableEntry對象的descriptor屬性中傳入weblogic.cache.RefWrapper對應的ObjectStreamClass對象。該類實現了Externalizable接口,同時該類的readExternal方法如下:

public void readExternal(ObjectInput oi) throws IOException, ClassNotFoundException {
    Object o = oi.readObject();
    if (o != null) {
        if (nosoftrefs) {
            this.hardref = o;
        } else {
            this.softref = new SoftReference(o);
        }
    }
}

在這個in.readObject()處打個斷點,,按照文章前面提到的數據發送流程發送數據后,再進入readObject跟幾步,利用棧如下:

可以發現程序又進入了readObjectFromPreDiabloPeer方法。原因在于默認傳入的ObjectInput和在MsgAbbrevInputStream中被readObject的是同一個流,依然會受白名單的影響。那么如何把這個流替換成其他的呢?其實也很簡單。

在Externalizable接口的實現類中,很常見的會看見這種寫法,這里以com.tangosol.coherence.servlet.AttributeHolder為例

public void readExternal(DataInput in) throws IOException {
    this.m_sName = ExternalizableHelper.readUTF(in);
    this.m_oValue = ExternalizableHelper.readObject(in);
    this.m_fActivationListener = in.readBoolean();
    this.m_fBindingListener = in.readBoolean();
    this.m_fLocal = in.readBoolean();
}

它在反序列化的過程中使用的是ExternalizableHelper.readObject方法。它在反序列化過程中根據序列化的數據類型不同,存在許多自定義的邏輯。其中在反序列化利用鏈中最常用是下面這兩種,我們重點關注它們對流是否存在轉換和處理

第一個是反序列化實現了ExternalizableLite接口的類。

try {
  Class clz = loadClass(sClass, loader, inWrapper == null ? null : inWrapper.getClassLoader());
  if (in instanceof ObjectInputStream) {
      ObjectInputStream ois = (ObjectInputStream)in;
      if (!checkObjectInputFilter(clz, ois)) {
          throw new InvalidClassException("Deserialization of class " + sClass + " was rejected");
      }
  }
  value = (ExternalizableLite)clz.newInstance();
} catch (InstantiationException var7) {
  throw new IOException("Unable to instantiate an instance of class '" + sClass + "'; this is most likely due to a missing public no-args constructor: " + var7 + "" + getStackTrace(var7) + "Class: " + sClass + "ClassLoader: " + loader + "ContextClassLoader: " + getContextClassLoader());
} catch (Exception var8) {
  throw new IOException("Class initialization failed: " + var8 + "" + getStackTrace(var8) + "Class: " + sClass + "ClassLoader: " + loader + "ContextClassLoader: " + getContextClassLoader(), var8);
}
if (loader != null) {
  if (inWrapper == null) {
      in = new WrapperDataInputStream((DataInput)in, loader);
  } else if (loader != inWrapper.getClassLoader()) {
      inWrapper.setClassLoader(loader);
  }
}
value.readExternal((DataInput)in);
if (value instanceof SerializerAware) {
  ((SerializerAware)value).setContextSerializer(ensureSerializer(loader));
}

首先是一段黑名單判斷,這應該是之前某個二次反序列化話的補丁。黑名單后就會newInstance,然后會生成一個新的WrapperDataInputStream對象,這看起來像是對流進行了轉化,但其實只是一層封裝,在實際的readObject過程中還是使用的原始流。

if (loader != null) {
  if (inWrapper == null) {
      in = new WrapperDataInputStream((DataInput)in, loader);
  } else if (loader != inWrapper.getClassLoader()) {
      inWrapper.setClassLoader(loader);
  }
}

因此重點需要關注第二個readSerializable方法了。這個方法是對常規的序列化的封裝。在執行readObject前存在這樣一段代碼:

ObjectInput streamObj = getObjectInput(in, loader);

在高版本的Weblogic中,最終是執行下面這段代碼:

public ObjectInput getObjectInput(DataInput in, ClassLoader loader, boolean fForceNew) throws IOException {
    if (!fForceNew && in instanceof WLSObjectInputStream) {
        return (ObjectInput)in;
    } else {
        InputStream inStream = this.getInputStream(in, fForceNew);
        loader = loader == null && in instanceof WrapperDataInputStream ? ((WrapperDataInputStream)in).getClassLoader() : loader;
        return (ObjectInput)(loader == null && in instanceof FilteringObjectInputStream ? (ObjectInput)in : new WLSObjectInputStream(inStream, RemoteObjectReplacer.getReplacer(), new ClassLoaderResolver(loader), loader, this.setFilter));
    }
}

在T3反序列化中,會生成一個新的WLSObjectInputStream對象作為流,從而擺脫了白名單。

下面是測試的調用棧。

在進入WLSObjectInputStream的readObject后,構建符合要求的數據流,即可正常反序列化,在這一步的實操中,我利用程序自身的序列化方法進行序列化的數據,在反序列化時都失敗了,可能需要對字節碼的一些字段進行手動修改,本文只提出T3協議的繞過方法,后續的WLSObjectInputStream的readObject實現,有興趣的師傅可以自行嘗試,期待你的分享文章。