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

目录

序列化

反序列化

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.hqwc.cn/news/541090.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Rocketmq专题-01 v5版单机部署篇

Rocketmq专题 注&#xff1a; 本教程由羞涩梦整理同步发布&#xff0c;本人技术分享站点&#xff1a;blog.hukanfa.com 转发本文请备注原文链接&#xff0c;本文内容整理日期&#xff1a;2024-01-28 csdn 博客名称&#xff1a;五维空间-影子&#xff0c;欢迎关注 说明 地址…

通过spring boot/redis/aspect 防止表单重复提交【防抖】

一、啥是防抖 所谓防抖&#xff0c;一是防用户手抖&#xff0c;二是防网络抖动。在Web系统中&#xff0c;表单提交是一个非常常见的功能&#xff0c;如果不加控制&#xff0c;容易因为用户的误操作或网络延迟导致同一请求被发送多次&#xff0c;进而生成重复的数据记录。要针…

分布式思想

1、单体架构设计存在的问题 传统项目采用单体架构设计,虽然可以在一定的程度上解决企业问题,但是如果功能模块众多,并且将来需要二次开发.由于模块都是部署到同一台tomcat服务器中,如果其中某个模块代码出现了问题,将直接影响整个tomcat服务器运行. 这样的设计耦合性太高.不便…

1335:【例2-4】连通块

【算法分析】 设数组vis&#xff0c;vis[i][j]表示(i,j)位置已经访问过。遍历地图中的每个位置&#xff0c;尝试从每个位置开始进行搜索。如果该位置不是0且没有访问过&#xff0c;那么访问该位置&#xff0c;并尝试从其上下左右四个位置开始搜索。在看一个新的位置时&#xff…

C++ //练习 10.34 使用reverse_iterator逆序打印一个vector。

C Primer&#xff08;第5版&#xff09; 练习 10.34 练习 10.34 使用reverse_iterator逆序打印一个vector。 环境&#xff1a;Linux Ubuntu&#xff08;云服务器&#xff09; 工具&#xff1a;vim 代码块 /**************************************************************…

湖北省建筑安全员C证考试通过后,如何在各平台快速查询

湖北省建筑安全员C证考试通过后&#xff0c;如何在各平台快速查询&#xff1f; 2024年湖北省建筑安全员C证&#xff08;建安C&#xff09;证书查询 蛮多人考过建筑安全员C证不知道在哪里查询&#xff0c;建筑行业的安全员C证也称之为专职安全员&#xff0c;建筑安全员ABC /三…

TCP的三次握手和4次挥手

一、首先讲一下TCP的由来 最开始&#xff0c;人们考虑到将网络信息的呼唤与回应进行规范&#xff0c;达成一种公认的协议&#xff0c;就好像没有交通规则的路口设定交通规则。 人们设计出完美的OSI协议&#xff0c;这个协议包含七个层次由下到上分别是&#xff1a; 物理层&…

RuoYi-Vue使用RestTemplate无法通过@Autowired注入报错

A component required a bean of type org.springframework.web.client.RestTemplate that could not be found. 解决方法&#xff1a; 将ruoyi-framework模块下找到ApplicationConfig这个配置类使用Bean注入&#xff1a; /*** RestTemplate配置*/Beanpublic RestTemplate r…

Linux信号灯

概念&#xff1a;是不同进程间或一个给定进程内部不同线程间同步的机制。类似我们的 PV操作概念&#xff1a; 生产者和消费者场景 &#xff30;(&#xff33;) 含义如下:if (信号量的值大于0) { 申请资源的任务继续运行&#xff1b;信号量的值减一&#xff1b;} else { 申…

防范服务器被攻击:查询IP地址的重要性与方法

在当今数字化时代&#xff0c;服务器扮演着重要的角色&#xff0c;为企业、组织和个人提供各种网络服务。然而&#xff0c;服务器也成为了网络攻击者的目标之一&#xff0c;可能面临各种安全威胁&#xff0c;例如DDoS攻击、恶意软件攻击、数据泄露等。为了有效地防范服务器被攻…

matplotlib-散点图

日期&#xff1a;2024.03.14 内容&#xff1a;将matplotlib的常用方法做一个记录&#xff0c;方便后续查找。 # 引入需要使用的库 from matplotlib import pyplot as plt# 设置画布大小 plt.figure(figsize(20,8),dpi 300)# 全局设置中文字体 plt.rcParams[font.sans-serif]…

项目性能优化—性能优化的指标、目标

项目性能优化—性能优化的指标、目标 性能优化的终极目标是什么 性能优化的目标实际上是为了更好的用户体验&#xff1a; 一般我们认为用户体验是下面的公式&#xff1a; 用户体验 产品设计&#xff08;非技术&#xff09; 系统性能 ≈ 系统性能 快 那什么样的体验叫快呢…