Weblogic T3协议白名单绕过方法探索

一颗小胡椒2023-06-29 15:45:42

去年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实现,有兴趣的师傅可以自行尝试,期待你的分享文章。

weblogic白名单
本作品采用《CC 协议》,转载必须注明作者和本文链接
去年weblogic白名单时研究了下怎么绕过,总结出了下面的思路,本想再找找有无新的攻击面的思路,但是找了几次都没找到,后来就搁置了。readMsgAbbrevs函数就会对流中的序列化数据进行反序列化,调用的是InboundMsgAbbrev类的readObject方法,并存储在栈中。这里也就是之前weblogic的漏洞会触发的readObject的地方。但是在21年4月的补丁中,Weblogic使用了白名单,只有以下七种类可以被反序列化,因此所有Weblogic原本的漏洞都无法使用。
验证环境weblogic/wsee/jaxws/WLSServletAdapter.class的handle方法打上断点访问http://127.0.0.1:7001/wls-wsat/CoordinatorPortType关于T3协议T3协议是Weblogic用于通信的独有的一个协议,Weblogic Server的RMI通信使用它在其他区的Java程序传输数据。T3协议的组成这里借一张图解释一下关于 T3协议的组成ac ed 00 05是反序列化标志,而在 T3 协议中每个序列化数据包前面都有fe 01 00 00,所以 T3 的序列化标志为fe 01 00 00 ac ed 00 05并且在发送T3协议的时候 还可以发送多个序列化数据 ,可以替换其中一个的序列化数据 实现反序列化攻击。
该漏洞是继CVE-2015-4852、CVE-2016-0638、CVE-2016-3510之后的又一个重量级反序列化漏洞。
近日,国家信息安全漏洞库(CNNVD)收到Oracle WebLogic wls9-async反序列化远程命令执行漏洞(CNNVD-201904-961)情况的报送。攻击者可利用该漏洞在未授权的情况下发送攻击数据,实现远程代码执行。WebLogic 10.X、WebLogic 12.1.3等版本均受漏洞影响。目前, Oracle官方未发布漏洞补丁,但可以通过临时解决措施缓解漏洞造成的危害,建议用户
腾讯云安全运营中心监测到, Oracle发布了2022年1月的安全更新补丁,包含Oracle产品系列中的497个新安全补丁。此次更新涉及Oracle WebLogic Server多个高危漏洞,包括CVE-2022-21306,CVE-2022-21292,CVE-2022-21371等。建议及时安装此补丁更新,和此公告中的其他补丁。
部分getshell漏洞汇总
2022-07-20 10:12:45
即可未授权访问console后台,但是权限比较低备注:此处会出现个问题,在复现的环境中直接拼接
最近两个月我一直在做拒绝服务漏洞相关的时间,并收获了Spring和Weblogic的两个CVE但DoS漏洞终归是鸡肋洞,并没有太大的意义,比如之前有人说我只会水垃圾洞而已,所以在以后可能打算做其他方向早上和pyn3rd师傅聊天
接到一个紧急测试任务,只有一个目标名称和一个ip。这样的话webshell的url就无法正常访问了)带上cookie即可正常连接。连接成功后,为了稳定webshell,我们尝试将webshell写入到根目录和静态文件的目录,但是仍会受到强制跳转的影响。于是将webshell内容写入到了在登陆前就能访问的jsp正常文件中,来稳定shell。
成功getshell后通过冰蝎上传了一个哥斯拉shell接下来就是socks5代理了,上传一个frp后发现服务端关了,事发突然并没有做什么权限维持,到手的shell飞了经过分析和思考,造成这种情况的原因是直接拿了编译好的frp没做免杀,也许内网有全流量,设备报警提醒了,管理员发现异常后直接关机了。
0x03 单机信息搜集tasklist /svc命令将获取到的进程列表与本地杀软进程进行对比后发现存在:ZhuDongFangYu.exe、aliyundun.exe、aliyun_assist_service.exe等进程。netstat -ano命令下发现1433、3389是开着的,但由于这台主机为内网,而且存在360主动防御,所以暂时无法实现文件落地、执行添加/修改用户密码、端口转发等操作。
一颗小胡椒
暂无描述