【Web】浅聊Java反序列化之玩转Hessian反序列化的前置知识

news/2024/5/19 22:54:40 标签: java, java反序列化, Hessian, Hessian反序列化, ctf, web

目录

序列化

反序列化

Hessian1.0-toc" style="margin-left:40px;">Hessian1.0

Hessian2.0-toc" style="margin-left:40px;">Hessian2.0

Hessian%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%A0%B8%E5%BF%83%EF%BC%9AMapDeserializer%23readMap%E7%9A%84%E5%88%A9%E7%94%A8-toc" style="margin-left:0px;">Hessian反序列化核心:MapDeserializer#readMap的利用

总结


序列化

HessianOutput&Hessian2Output都是抽象类AbstractHessianOutput的实现类

HessianOutput#writeObject和Hessian2Output#writeObject的写法是一样的

首先获取序列化器 Serializer 的实现类,并调用其 writeObject 方法序列化数据 

public void writeObject(Object object) throws IOException {
        if (object == null) {
            this.writeNull();
        } else {
            Serializer serializer = this.findSerializerFactory().getObjectSerializer(object.getClass());
            serializer.writeObject(object, this);
        }
    }

对于自定义类型,默认情况下会用 UnsafeSerializer来进行序列化相关操作(这里先不讲为什么,反序列化部分会讲的,简单类比即可,莫急)

关注UnsafeSerializer#writeObject,该方法兼容了 Hessian/Hessian2 两种协议的数据结构,会调用传入的AbstractHessianOutput的writeObjectBegin 方法开始写入数据

public void writeObject(Object obj, AbstractHessianOutput out) throws IOException {
        if (!out.addRef(obj)) {
            Class<?> cl = obj.getClass();
            int ref = out.writeObjectBegin(cl.getName());
            if (ref >= 0) {
                this.writeInstance(obj, out);
            } else if (ref == -1) {
                this.writeDefinition20(out);
                out.writeObjectBegin(cl.getName());
                this.writeInstance(obj, out);
            } else {
                this.writeObject10(obj, out);
            }

        }
    }

看一下AbstractHessianOutput#writeObjectBegin

public int writeObjectBegin(String type) throws IOException {
        this.writeMapBegin(type);
        return -2;
    }

虽然writeObjectBegin 是 AbstractHessianOutput 的方法,但并不是每个实现类都对其进行了重写

HessianOutput就没有,而Hessian2Output对其进行了以下重写

public int writeObjectBegin(String type) throws IOException {
        int newRef = this._classRefs.size();
        int ref = this._classRefs.put(type, newRef, false);
        if (newRef != ref) {
            if (8192 < this._offset + 32) {
                this.flushBuffer();
            }

            if (ref <= 15) {
                this._buffer[this._offset++] = (byte)(96 + ref);
            } else {
                this._buffer[this._offset++] = 79;
                this.writeInt(ref);
            }

            return ref;
        } else {
            if (8192 < this._offset + 32) {
                this.flushBuffer();
            }

            this._buffer[this._offset++] = 67;
            this.writeString(type);
            return -1;
        }
    }

那么也就是说写入自定义数据类型(Object)时,Hessian1.0会调用 writeMapBegin 方法将其标记为 Map 类型,调用writeObject10来写入自定义数据。Hessian2.0将会调用 writeDefinition20 和 Hessian2Output#writeObjectBegin 方法写入自定义数据,就不将其标记为 Map 类型

反序列化

HessianInput&Hessian2Input都是抽象类AbstractHessianInput的实现类

Hessian1.0">Hessian1.0

对于Hessian1.0,正如我们上文所说,在写入自定义类型时会将其标记为 Map 类型,所以HessianInput 中,没有针对 Object 的读取,而是都将其作为 Map 读取

由于Hessian会将序列化的结果处理成一个Map,所以序列化结果的第一个byte总为M(ASCII为77),然后HessianInput#readObject的那一长串switch case根据的tag就是取的第一个byte的ASCII值

跟一下HessianInput#readObject,果然停到了77

跟进_serializerFactory.readMap

public Object readMap(AbstractHessianInput in, String type) throws HessianProtocolException, IOException {
        Deserializer deserializer = this.getDeserializer(type);
        if (deserializer != null) {
            return deserializer.readMap(in);
        } else if (this._hashMapDeserializer != null) {
            return this._hashMapDeserializer.readMap(in);
        } else {
            this._hashMapDeserializer = new MapDeserializer(HashMap.class);
            return this._hashMapDeserializer.readMap(in);
        }
    }

 先是调用getDeserializer

public Deserializer getDeserializer(Class cl) throws HessianProtocolException {
        Deserializer deserializer;
        if (this._cachedDeserializerMap != null) {
            deserializer = (Deserializer)this._cachedDeserializerMap.get(cl);
            if (deserializer != null) {
                return deserializer;
            }
        }

        deserializer = this.loadDeserializer(cl);
        if (this._cachedDeserializerMap == null) {
            this._cachedDeserializerMap = new ConcurrentHashMap(8);
        }

        this._cachedDeserializerMap.put(cl, deserializer);
        return deserializer;
    }

接着调用loadDeserializer

protected Deserializer loadDeserializer(Class cl) throws HessianProtocolException {
        Deserializer deserializer = null;

        for(int i = 0; deserializer == null && this._factories != null && i < this._factories.size(); ++i) {
            AbstractSerializerFactory factory = (AbstractSerializerFactory)this._factories.get(i);
            deserializer = factory.getDeserializer(cl);
        }

        if (deserializer != null) {
            return deserializer;
        } else {
            deserializer = this._contextFactory.getDeserializer(cl.getName());
            if (deserializer != null) {
                return deserializer;
            } else {
                ContextSerializerFactory factory = null;
                if (cl.getClassLoader() != null) {
                    factory = ContextSerializerFactory.create(cl.getClassLoader());
                } else {
                    factory = ContextSerializerFactory.create(_systemClassLoader);
                }

                deserializer = factory.getDeserializer(cl.getName());
                if (deserializer != null) {
                    return deserializer;
                } else {
                    deserializer = factory.getCustomDeserializer(cl);
                    if (deserializer != null) {
                        return deserializer;
                    } else {
                        Object deserializer;
                        if (Collection.class.isAssignableFrom(cl)) {
                            deserializer = new CollectionDeserializer(cl);
                        } else if (Map.class.isAssignableFrom(cl)) {
                            deserializer = new MapDeserializer(cl);
                        } else if (Iterator.class.isAssignableFrom(cl)) {
                            deserializer = IteratorDeserializer.create();
                        } else if (Annotation.class.isAssignableFrom(cl)) {
                            deserializer = new AnnotationDeserializer(cl);
                        } else if (cl.isInterface()) {
                            deserializer = new ObjectDeserializer(cl);
                        } else if (cl.isArray()) {
                            deserializer = new ArrayDeserializer(cl.getComponentType());
                        } else if (Enumeration.class.isAssignableFrom(cl)) {
                            deserializer = EnumerationDeserializer.create();
                        } else if (Enum.class.isAssignableFrom(cl)) {
                            deserializer = new EnumDeserializer(cl);
                        } else if (Class.class.equals(cl)) {
                            deserializer = new ClassDeserializer(this.getClassLoader());
                        } else {
                            deserializer = this.getDefaultDeserializer(cl);
                        }

                        return (Deserializer)deserializer;
                    }
                }
            }
        }
    }

我们主要看下面这两段逻辑

代码使用 Map.class.isAssignableFrom(cl) 条件来检查变量 cl 是否实现了 Map 接口。

如果条件成立(即 cl 实现了 Map 接口),则代码创建一个 MapDeserializer 对象,并将变量 cl 作为参数传递给构造方法。

如果那一连串条件判断都不符合(即自定义类),则调用getDefaultDeserializer

跟进getDefaultDeserializer

protected Deserializer getDefaultDeserializer(Class cl) {
        if (InputStream.class.equals(cl)) {
            return InputStreamDeserializer.DESER;
        } else {
            return (Deserializer)(this._isEnableUnsafeSerializer ? new UnsafeDeserializer(cl, this._fieldDeserializerFactory) : new JavaDeserializer(cl, this._fieldDeserializerFactory));
        }
    }

 而_isEnableUnsafeSerializer默认为true

this._isEnableUnsafeSerializer = UnsafeSerializer.isEnabled() && UnsafeDeserializer.isEnabled();

这也就是说,对于自定义类型,默认情况会用 UnsafeDeserializer来进行反序列化相关操作 

这里我要多说一句,77那一处仅仅是传入的类被“标记为Map”,而不是让这个类强转为Map,具体说就是让第一个byte为M,因此这并不影响后续getDeserializer还是得看该类本身的属性,如果其是自定义类,那么还是会得到UnsafeDeserializer

比如我这里自定义了一个Person类,反序列化虽然进到77,但不妨碍取的还是UnsafeDeserializer

再者,我反序列化了一个HashMap,同样是进到77,但取到的则是MapDeserializer

 OK小插曲到此为止。

取到deserializer之后,便会调用deserializer.readMap

Hessian反序列化漏洞的利用的正是MapDeserializer.readMap,这里暂按下不表。

Hessian2.0">Hessian2.0

对于Hessian2.0,正如一开始所说,序列化时Hessian2.0不会将传入的类标记为Map类型,所以在进Switch判断时就纯看自身了。

但宗旨是不变的,你是什么类,实现什么接口,你就会取到什么对应的deserializer

如果你想取到MapDeserializer,那传入的类就必须与Map相关

我们尝试用Hessian2Input来反序列化一个HashMap

Hessian2Input#readObject进到72

成功取到MapDeserializer

取到MapDeserializer 之后,便会调用其readMap方法

Hessian%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%A0%B8%E5%BF%83%EF%BC%9AMapDeserializer%23readMap%E7%9A%84%E5%88%A9%E7%94%A8">Hessian反序列化核心:MapDeserializer#readMap的利用

public Object readMap(AbstractHessianInput in) throws IOException {
        Object map;
        if (this._type == null) {
            map = new HashMap();
        } else if (this._type.equals(Map.class)) {
            map = new HashMap();
        } else if (this._type.equals(SortedMap.class)) {
            map = new TreeMap();
        } else {
            try {
                map = (Map)this._ctor.newInstance();
            } catch (Exception var4) {
                throw new IOExceptionWrapper(var4);
            }
        }

        in.addRef(map);

        while(!in.isEnd()) {
            ((Map)map).put(in.readObject(), in.readObject());
        }

        in.readEnd();
        return map;
    }

啰嗦地解读一下

  1. 首先,代码定义了一个变量 map 用于存储读取后的映射对象。

  2. 接着,通过一系列条件判断来确定要实例化的具体映射类型:

    • 如果 _type 为 null,则实例化一个 HashMap 对象并赋值给 map
    • 如果 _type 类型为 Map.class,则同样实例化一个 HashMap 对象。
    • 如果 _type 类型为 SortedMap.class,则实例化一个 TreeMap 对象。
    • 否则,尝试通过反射实例化 _ctor 表示的类,并将结果赋值给 map。如果实例化过程中出现异常,则抛出 IOException。
  3. 在确定了要实例化的映射类型后,代码调用 in.addRef(map) 将实例化后的映射对象添加到引用表中。

  4. 进入一个循环,通过 in.isEnd() 方法检查输入流是否结束。在循环中,代码通过 in.readObject() 方法读取键值对并将其放(put)进映射对象中。(注意这里调用的还是HessianInput&Hessian2Input的readObject方法,而非原生,只不过是目标是属性,比如一个HashMap里存了一个key为EqualsBean,那么就会按照EqualsBean的特性走一遍switchcase等等等等流程再存进HashMap里)

  5. 循环直到输入流结束标记被读取。

  6. 最后,代码调用 in.readEnd() 方法读取映射的结束标记,并返回读取、填充后的映射对象 map

其实说那么多,关键就在那个put的点上

map.put对于HashMap会触发key.hashCode()、key.equals(k),而对于TreeMap会触发key.compareTo()

总结

Hessian反序列化Map类型的对象的时候,会自动调用其put方法,而put方法又会牵引出各种相关利用链打法。


http://www.niftyadmin.cn/n/5423296.html

相关文章

开发者须知,苹果商店关键词覆盖

一、APP名称要谨慎 标题里的关键词会被算法优先收录&#xff0c;权重占比最大。 小柚建议老板取一个有记忆点的标题&#xff0c;要展示 App 的功能与独特性&#xff0c;比如说“柚柚陪玩”这个标题一眼就能知道这是个陪玩App。注意取标题不要和产品看起来毫无关系&#xff0c;…

【wine】WINEDEBUG 分析mame模拟器不能加载roms下面的游戏 可以调整参数,快速启动其中一个游戏kof98

故障现象&#xff0c;MAME启动后&#xff0c;游戏都没有识别 添加日志输出&#xff0c;重新启动wine #!/bin/bashexport WINEPREFIX$(pwd)/.wine export WINESERVER$(pwd)/bin/wineserver export WINELOADER$(pwd)/bin/wine export WINEDEBUG"file,mame,warn,err"…

读书笔记之《机器与人》:AI如何重构工作方式和流程?

《机器与人: 埃森哲论新人工智能》作者是【美】保罗•多尔蒂和詹姆斯•威尔逊 &#xff0c;原作名: Human Machine: Reimagining Work in the Age of AI&#xff0c;2018年出版。 保罗•多尔蒂&#xff08;PAUL DAUGHERTYH&#xff09;&#xff1a;埃森哲首席技术官和创新官、…

备案算法才能开启AI新纪元,解锁无限可能

深度合成服务算法的发展&#xff0c;无疑正在以前所未有的速度改变众多行业的工作方式和业务模式。作为引领新一轮科技革命和产业变革的重要驱动力量&#xff0c;AI技术正在逐步渗透到社会的各个角落&#xff0c;从而引发了一场深刻的变革。以AI问答机器人为例&#xff0c;它们…

某阿系影城网爬虫JS逆向

本次逆向目标网站如下&#xff0c;使用base64解码获得 aHR0cHM6Ly9oNWxhcmsueXVla2V5dW4uY29tL2ZpbG0vaW5kZXguaHRtbD93YXBpZD1GWVlDX0g1X1BST0RfU19NUFMmc3RhbXA9MTcxMDExNzc5NDM0NiZzcG09YTJvZjYubG9jYXRpb25faW5kZXhfcGFnZS4wLjA 打开网站&#xff0c;发起请求后&#xff0c…

Linux操作系统-06-进程与服务管理

使用ps命令查看进程。包括过滤进程信息 使用systemctl命令管理和运行Linux服务 进程&#xff08;Process&#xff09;&#xff1a;操作系统正在运行的应用程序。任意一个进程&#xff0c;都会消耗CPU和内存资源&#xff0c; 服务&#xff08;Service&#xff09;&#xff1a…

鲜花销售小程序|基于微信小程序的鲜花销售系统设计与实现(源码+数据库+文档)

鲜花销售小程序目录 目录 基于微信小程序的鲜花销售系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1前台功能模块 2、后台功能模块 1、管理员功能模块 四、数据库设计 1、实体ER图 2、具体的表设计如下所示&#xff1a; 五、核心代码 六、论文参考 七、…

为什么Java不支持多继承

1、典型回答 在 Java 语言中&#xff0c;不支持多继承的主要原因是为了简化语言设计和避免潜在的问题&#xff08;如菱形继承&#xff09;以及避免多重继承的层次膨胀&#xff0c;同时又因为在实际工作中&#xff0c;确实很少用到多继承&#xff0c;所以在Java语言中&#xff0…