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

news2024/12/25 0:43:21

目录

序列化

反序列化

Hessian1.0

Hessian2.0

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,正如我们上文所说,在写入自定义类型时会将其标记为 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不会将传入的类标记为Map类型,所以在进Switch判断时就纯看自身了。

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

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

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

Hessian2Input#readObject进到72

成功取到MapDeserializer

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

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.coloradmin.cn/o/1508218.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux:kubernetes(k8s)Deployment的操作(12)

创建deployment 命令 kubectl create deploy nginx-deploy --imagenginx:1.7.9 再去使用以下命令分别查询 ubectl get deploy kubectl get replicaset kubectl get pod 他是一个层层嵌套的一个关系 首先是创建了一个 deploy 里面包含着replicaset replicaset里面含有…

文档版面分析数据集整理

版面分析数据集 这里整理了常用版面分析数据集&#xff0c;持续更新中&#xff1a; publaynet数据集CDLA数据集TableBank数据集D4LA 数据集DocLayNet文档布局分割数据集M6Doc数据集 版面分析数据集多为目标检测数据集&#xff0c;除了开源数据&#xff0c;用户还可使用合成工…

AIGC——ControlNet模型的原理

简介 ControlNet旨在控制预训练的大型扩散模型&#xff0c;以支持额外的输入条件。ControlNet能够以端到端的方式学习特定任务的条件&#xff0c;即使在训练数据集很小的情况下&#xff08;<50k&#xff09;&#xff0c;也能保持稳健性。此外&#xff0c;训练ControlNet的速…

更改npm的镜像地址

使用如下命令查看镜像地址 npm config get registry 修改npm镜像的地址 npm config set registry https://registry.npmmirror.com/

​如何防止网络攻击?

应对不同类型网络攻击的最佳途径是“知己”、“知彼”&#xff0c;在了解它们的工作原理、能够识别其手段、方法及意图的前提下&#xff0c;找出针对性的应对文案。今天&#xff0c;就为大家总结以下防止不同类型网络攻击的有效方法&#xff0c;希望无论是对个人、还是企业和组…

Linux调试器--gdb的介绍以及使用

文章目录 1.前言 ✒️2.介绍gdb✒️3.Debug模式和Release模式的区别✒️4.如何使用gdb✒️1️⃣.在debug模式下编译2️⃣.进入调试3️⃣ .调试命令集合⭐️⭐️ 1.前言 ✒️ &#x1f557;在我们之前的学习中已经学会了使用vim编译器编写c/c代码&#xff0c;但是对于一个程序员…

零基础自学C语言|自定义类型:结构体

✈结构体类型的声明 前面我们在学习操作符的时候&#xff0c;已经学习了结构体的知识&#xff0c;这里稍微复习一下。 &#x1f680;结构体回顾 结构是一些值的集合&#xff0c;这些值称为成员变量。结构的每个成员可以是不同类型的变量。 &#x1fa82;结构的声明 例如&a…

了解转义字符

了解转义字符 也许在前面的代码中你看到 \n , \0 很纳闷是啥。其实在字符中有⼀组特殊的字符是转义字符&#xff0c;转义字符顾名思义&#xff1a;转变原来的意思的字符。 比如&#xff1a;我们有字符 n &#xff0c;在字符串中打印的时候自然能打印出这个字符&#xff0c;如下…

OpenCV 图像的几何变换

一、图像缩放 1.API cv2.resize(src, dsize, fx0,fy0,interpolation cv2.INTER_LINEAR) 参数&#xff1a; ①src &#xff1a;输入图像 ②dsize&#xff1a;绝对尺寸 ③fx&#xff0c;fy&#xff1a;相对尺寸 ④interpolation&#xff1a;插值方法 2.代码演示 import cv2 …

项目管理类,PMP和软考哪个更实用?

如果你想转变职业发展方向&#xff0c;那么请仔细考虑你的工作定位。对于项目管理而言&#xff0c;PMP考试是专门为项目管理人员设计的。而与项目管理相关的软考考试主要包括软考集成和高级。不过&#xff0c;软考更多地关注计算机技术和软件专业的认证。以下是我对PMP和软考两…

【Redis】 缓存双写一致性

缓存双写一致性 给缓存设置过期时间&#xff0c;定期清理缓存并回写&#xff0c;是保证最终一致性的解决方案。 我们可以对存入缓存的数据设置过期时间&#xff0c;所有的写操作以数据库为准&#xff0c;对缓存操作只是尽最大努力即可。也就是说如果数据库写成功&#xff0c;缓…

业界主流数据加速技术路线

计算存储分离已经成为云计算的一种发展趋势。在计算存储分离之前&#xff0c;普遍采用的是传统的计算存储相互融合的架构&#xff0c;但是这种架构存在一定的问题&#xff0c;比如在集群扩容的时候会面临计算能力和存储能力相互不匹配的问题。用户在某些情况下只需要扩容计算能…

编译支持国密的抓包工具 WireShark

目录 前言WireShark支持国密的 WireShark小结前言 在上一篇文章支持国密的 Web 服务器中,我们搭建了支持国密的 Web 服务器,但是,我们使用 360 安全浏览器去访问,却出现了错误: 是我们的 Web 服务器没有配置好?在这里插入图片描述还是 360 安全浏览器不支持国密?还是两…

pycharm手动安装常用插件

下载插件 &#xff08;1&#xff09;下载地址&#xff1a;JetBrains Marketplace 这里以语言包为例子 2、中文语言包 进入pycharm中的设置&#xff0c;点击plugins,选从磁盘中安装插件

分享axios+signalr简单封装示例

Ajax Axios Axios 是一个基于 promise 网络请求库&#xff0c;作用于node.js 和浏览器中。 它是 isomorphic 的(即同一套代码可以运行在浏览器和node.js中)。在服务端它使用原生 node.js http 模块, 而在客户端 (浏览端) 则使用 XMLHttpRequests。 从浏览器创建 XMLHttpReque…

最新基于R语言lavaan结构方程模型(SEM)技术

原文链接&#xff1a;最新基于R语言lavaan结构方程模型&#xff08;SEM&#xff09;技术https://mp.weixin.qq.com/s?__bizMzUzNTczMDMxMg&mid2247596681&idx4&sn08753dd4d3e7bc492d750c0f06bba1b2&chksmfa823b6ecdf5b278ca0b94213391b5a222d1776743609cd3d14…

ChatGPT等AI使用的过程苦笑不得瞬间

引言&#xff1a; 在人工智能的浪潮中&#xff0c;我们见证了技术的飞速发展和智能应用的广泛渗透。特别是随着语言模型的进步&#xff0c;AI如ChatGPT、文心一言、通义千问、讯飞星火等已经成为人们日常生活和工作中不可或缺的助手。然而&#xff0c;与任何新兴技术一样&#…

在云端构建和部署工作负载的最佳方式是怎样的?

如果要问当今企业希望从云计算中获得什么&#xff0c;那么 “低延迟” 以及 “更接近客户” 可能会是很多企业的首要目标。低延迟可以带来诸多好处&#xff0c;如提升用户满意度、增加竞争优势、降低运营成本等&#xff1b;更接近客户则有助于降低网络拥塞、减少数据丢失、符合…

【算法】一类支持向量机OC-SVM

【算法】一类支持向量机OC-SVM 前言一类支持向量机OC-SVM 概念介绍示例编写数据集创建实现一类支持向量机OC-SVM完整的示例输出 前言 由于之前毕设期间主要的工具就是支持向量机&#xff0c;从基础的回归和分类到后来的优化&#xff0c;在接触到支持向量机还有一类支持向量机的…

可免费使用的AI平台汇总 + 常用赋能科研的AI工具推荐

赋能科研&#xff0c;AI工具助你飞跃学术巅峰&#xff01;(推荐收藏) 文章目录 赋能科研&#xff0c;AI工具助你飞跃学术巅峰&#xff01;(推荐收藏)一、可免费使用的AI平台汇总1. ChatGPT2. New Bing3. Slack4. POE5. Vercel6. 其他平台7. 特定功能平台8. 学术资源平台9. 中文…