System Online

Architect your
digital legacy.

ZIMU
WEB SEC

动态加载字节码

Java字节码(ByteCode)其实仅仅指的是Java虚拟机执行使用的一类指令,通常被存储在.class文件中

只要你的编译器能够将代码编译成.class文件,都可以在JVM虚拟机中运行

利用URLClassLoader加载远程class文件

ClassLoader 就是一个“加载器”,告诉Java虚拟机如何加载这个类。Java默认的
ClassLoader 就是根据类名来加载类,这个类名是类完整路径,如 java.lang.Runtime 

URLClassLoader 实际上是我们平时默认使用的 AppClassLoader 的父类,所以,我们解释 URLClassLoader 的工作过程实际上就是在解释默认的Java类加载器的工作流程

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

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

什么时候才会出现使用 Loader 寻找类的情况呢?当然是 非 file 协议的情况下,最常见的就是 http 协议

编译一个简单的HelloWorld程序,放在 http://localhost:8000/Hello.class

请求到我们的 /Hello.class 文件,并执行了文件里的字节码,输出了"Hello World"。

如果我们能够控制目标Java ClassLoader的基础路径为一个http服务器,则可以利用远程加载的方式执行任意代码

不管是加载远程class文件,还是本地的class或jar文件,Java都经历的是下面这三个方法调用:

loadClass 的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机制),在前面没有找到的情况下,执行 findClass
findClass 的作用是根据基础URL指定的方式来加载类的字节码,就像上一节中说到的,可能会在本地文件系统、jar包或远程http服务器上读取字节码,然后交给 defineClass
defineClass 的作用是处理前面传入的字节码,将其处理成真正的Java类

真正核心的部分其实是 defineClass ,他决定了如何将一段字节流转变成一个Java类,Java 默认的 ClassLoader#defineClass 是一个native方法,逻辑在JVM的C语言代码中

让系统的 defineClass 来直接加载字节码:

package org.example;

import java.lang.reflect.Method;
import java.util.Base64;
public class HelloDefineClass {
    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("yv66vgAAADQAGwoABgANCQAOAA8IABAKABEAEgcAEwcAFA" +
                                                 "EABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApTb3VyY2VG" +
                                                 "aWxlAQAKSGVsbG8uamF2YQwABwAIBwAVDAAWABcBAAtIZWxsbyBXb3JsZAcAGAwAGQAaAQAFSGVsbG8BABBqYXZh" +
                                                 "L2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEva" +
                                                 "W8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACEABQAGAAAAAAABAAEABwAIAAEACQAAAC0AAgA" +
                                                 "BAAAADSq3AAGyAAISA7YABLEAAAABAAoAAAAOAAMAAAACAAQABAAMAAUAAQALAAAAAgAM");
        Class hello = (Class)defineClass.invoke(ClassLoader.getSystemClassLoader(), "Hello", code, 0, code.length);
        hello.newInstance();
    }
}

因为系统的 ClassLoader#defineClass 是一个保护属性,所以我们无法直接在外部访问,不得不使用反射的形式来调用。 在实际场景中,因为defineClass方法作用域是不开放的,所以攻击者很少能直接利用到它,但它却是我们常用的一个攻击链 TemplatesImpl 的基石

利用TemplatesImpl加载字节码

大部分上层开发者不会直接使用到defineClass方法,但是Java底层还是有一些类用到了它(否则他 也没存在的价值了对吧),这就是 TemplatesImpl

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl 这个类中定义了一个内部类 TransletClassLoader :

static final class TransletClassLoader extends ClassLoader {
        private final Map<String, Class<?>> _loadedExternalExtensionFunctions;

         TransletClassLoader(ClassLoader parent) {
             super(parent);
            _loadedExternalExtensionFunctions = null;
        }

        TransletClassLoader(ClassLoader parent, Map<String, Class<?>> mapEF) {
            super(parent);
            _loadedExternalExtensionFunctions = mapEF;
        }

        @Override
        public Class<?> loadClass(String name) throws ClassNotFoundException {
            Class<?> ret = null;
            // The _loadedExternalExtensionFunctions will be empty when the
            // SecurityManager is not set and the FSP is turned off
            if (_loadedExternalExtensionFunctions != null) {
                ret = _loadedExternalExtensionFunctions.get(name);
            }
            if (ret == null) {
                ret = super.loadClass(name);
            }
            return ret;
         }

        /**
         * Access to final protected superclass member from outer class.
         */
        Class<?> defineClass(final byte[] b) {
            return defineClass(null, b, 0, b.length);
        }

        Class<?> defineClass(final byte[] b, ProtectionDomain pd) {
            return defineClass(null, b, 0, b.length, pd);
        }
    }

重写了 defineClass 方法,这里的 defineClass 方法,可以被类外部调用

利用链如下

TemplatesImpl#getOutputProperties() -> TemplatesImpl#newTransformer() ->
TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses()
-> TransletClassLoader#defineClass()

我们从TemplatesImpl#newTransformer()开始制造一个POC

package org.example;
import java.lang.reflect.Field;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.util.Base64;
public class TemplatesImpl_first {
    public static void main(String[] args) throws Exception {
// source: bytecodes/HelloTemplateImpl.java
        byte[] code = Base64.getDecoder().decode("yv66vgAAADQAIQoABgASCQATABQIABUKABYAFwcAGAcAGQEACXRyYW5zZm9ybQEAcihMY29tL3N1" +
                                "bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNo" +
                                "ZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9Ma" +
                                "W5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE" +
                                "9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R" +
                                "0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50" +
                                "ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0" +
                                "PgEAAygpVgEAClNvdXJjZUZpbGUBABdIZWxsb1RlbXBsYXRlc0ltcGwuamF2YQwADgAPB" +
                                "wAbDAAcAB0BABNIZWxsbyBUZW1wbGF0ZXNJbXBsBwAeDAAfACABABJIZWxsb1RlbXBsYXRlc0ltcGwBAEBjb20v" +
                                "c3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29t" +
                                "L3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQAQamF2YS9sYW5n" +
                                "L1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTd" +
                                "HJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACEABQAGAAAAAAADAAEA" +
                                "BwAIAAIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAAIAAsAAAAEAAEADAABAAcADQACAAkAAAAZAAA" +
                                "ABAAAAAGxAAAAAQAKAAAABgABAAAACgALAAAABAABAAwAAQAOAA8AAQAJAAAALQACAAEAAAANKrcA" +
                                "AbIAAhIDtgAEsQAAAAEACgAAAA4AAwAAAA0ABAAOAAwADwABABAAAAACABE=");
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][] {code});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
        obj.newTransformer();
    }
    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);
    }

}

其中, setFieldValue 方法用来设置私有属性,可见,这里我设置了三个属性: _bytecodes 、 _name和 _tfactory 。 _bytecodes 是由字节码组成的数组; _name 可以是任意字符串,只要不为null即可;
_tfactory 需要是一个 TransformerFactoryImpl 对象,因为
TemplatesImpl#defineTransletClasses() 方法里有调用到
_tfactory.getExternalExtensionsMap() ,如果是null会出错。
另外,值得注意的是, TemplatesImpl 中对加载的字节码是有一定要求的:这个字节码对应的类必须
是 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet 的子类

构造一个特殊的类:

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
public class HelloTemplatesImpl extends AbstractTranslet {
public void transform(DOM document, SerializationHandler[] handlers)
throws TransletException {}
public void transform(DOM document, DTMAxisIterator iterator,
SerializationHandler handler) throws TransletException {}
public HelloTemplatesImpl() {
super();
System.out.println("Hello TemplatesImpl");
}
}

将其编译成字节码,即可被 TemplatesImpl 执行了

利用BCEL ClassLoader加载字节码

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

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.Repository;
public class HelloBCEL {
public static void main(String []args) throws Exception {
JavaClass cls = Repository.lookupClass(evil.Hello.class);
String code = Utility.encode(cls.getBytes(), true);
System.out.println(code);
}
}

BCEL ClassLoader用于加载这串特殊的“字节码”,并可以执行其中的代码:

这个类和前面的 TemplatesImpl 都出自于同一个第三方库,Apache Xalan

在Java 8u251的更新中,这个ClassLoader被移除了

posted @ 2026-02-01 20:32  zimu61  阅读(1)  评论(0)    收藏  举报